some fixes from jdahlin
[platform/upstream/gstreamer.git] / tools / gst-run.c
1 /* GStreamer
2  * Copyright (C) 2004 Thomas Vander Stichele <thomas@apestaart.org>
3  *
4  * gst-run.c: tool to launch GStreamer tools with correct major/minor
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <errno.h>
30 #include <popt.h>
31 #include <glib.h>
32
33 enum
34 {
35   ARG_MM = 1,
36   ARG_LIST_MM,
37   ARG_PRINT,
38   ARG_HELP
39 };
40
41 /* global statics for option parsing */
42 static gboolean _print = FALSE;
43 static gchar * _arg_mm = NULL;
44 static gboolean _arg_list_mm = FALSE;
45
46 /* callback to parse arguments */
47 static void
48 popt_callback (poptContext context, enum poptCallbackReason reason,
49                const struct poptOption *option, const char *arg, void *data)
50 {
51   if (reason == POPT_CALLBACK_REASON_OPTION)
52   {
53     switch (option->val)
54     {
55       case ARG_MM:
56         _arg_mm = g_strdup (arg);
57         break;
58       case ARG_LIST_MM:
59         _arg_list_mm = TRUE;
60         break;
61       case ARG_PRINT:
62         _print = TRUE;
63         break;
64       case ARG_HELP:
65         poptPrintHelp (context, stdout, 0);
66         g_print ("\n");
67         break;
68     }
69   }
70   else
71   {
72     g_print ("Unknown reason for callback\n");
73   }
74 }
75
76 /* popt options table for the wrapper */
77 static struct poptOption wrapper_options[] =
78 {
79   { NULL, '\0',
80     POPT_ARG_CALLBACK,
81     (void *) &popt_callback, 0, NULL, NULL },
82   { "help", '\0',
83     POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN,
84     NULL, ARG_HELP, ("Show help"), NULL },
85   { "?", '\0',
86     POPT_ARG_NONE | POPT_ARGFLAG_STRIP | POPT_ARGFLAG_ONEDASH
87                   | POPT_ARGFLAG_DOC_HIDDEN,
88     NULL, ARG_HELP, NULL, NULL },
89   /* We cheat by specifying -p as long "p" with onedash, so that it
90      also gets stripped properly from our arg flags */
91   { "p", '\0',
92     POPT_ARG_NONE | POPT_ARGFLAG_STRIP | POPT_ARGFLAG_ONEDASH
93                   | POPT_ARGFLAG_DOC_HIDDEN,
94     NULL, ARG_PRINT, NULL, NULL },
95   { "print", '\0',
96     POPT_ARG_NONE | POPT_ARGFLAG_STRIP,
97     NULL, ARG_PRINT, ("Print wrapped command line"), NULL },
98   { "gst-mm", '\0',
99     POPT_ARG_STRING | POPT_ARGFLAG_STRIP,
100     NULL, ARG_MM, ("Force major/minor version"), NULL },
101   { "gst-list-mm", '\0',
102     POPT_ARG_NONE | POPT_ARGFLAG_STRIP,
103     NULL, ARG_LIST_MM, ("List found major/minor versions"), NULL },
104   POPT_TABLEEND
105 };
106
107 /* helper table including our wrapper options */
108 static struct poptOption options[] =
109 {
110   { NULL, '\0', POPT_ARG_INCLUDE_TABLE, wrapper_options,  0,
111     "Wrapper options:", NULL},
112   POPT_TABLEEND
113 };
114
115
116 /* print out the major/minor, which is the hash key */
117 static void
118 hash_print_key (gchar * key, gchar * value)
119 {
120   g_print ("%s\n", (gchar *) key);
121 }
122
123 static void
124 find_highest_version (gchar * key, gchar * value, gchar ** highest)
125 {
126   if (*highest == NULL)
127   {
128     /* first value, so just set it */
129     *highest = key;
130   }
131   if (strcmp (key, *highest) > 0) *highest = key;
132 }
133
134 /* Libtool creates shell scripts named "base" that calls actual binaries as
135  * .libs/lt-base.  If we detect this is a libtool script, unmangle so we
136  * find the right binaries */
137 static void
138 unmangle_libtool (gchar ** dir, gchar ** base)
139 {
140   gchar *new_dir, *new_base;
141
142   if (!*dir) return;
143   if (!*base) return;
144
145   /* we assume libtool when base starts with lt- and dir ends with .libs */
146   if (!g_str_has_prefix (*base, "lt-")) return;
147   if (!g_str_has_suffix (*dir, ".libs")) return;
148
149   new_base = g_strdup (&((*base)[3]));
150   new_dir = g_path_get_dirname (*dir);
151   g_free (*base);
152   g_free (*dir);
153   *base = new_base;
154   *dir = new_dir;
155 }
156
157 /* Returns a directory path that contains the binary given as an argument.
158  * If the binary given contains a path, it gets looked for in that path.
159  * If it doesn't contain a path, it gets looked for in the standard path.
160  *
161  * The returned string is newly allocated.
162  */
163 gchar *
164 get_dir_of_binary (const gchar * binary)
165 {
166   gchar *base, *dir;
167   gchar *full;
168
169   base = g_path_get_basename (binary);
170   dir = g_path_get_dirname (binary);
171
172   /* if putting these two together yields the same as binary,
173    * then we have the right breakup.  If not, it's because no path was
174    * specified which caused get_basename to return "." */
175   full = g_build_filename (dir, base, NULL);
176
177   if (strcmp (full, binary) != 0)
178   {
179     if (strcmp (dir, ".") != 0)
180     {
181       g_warning ("This should not happen, g_path_get_dirname () has changed.");
182       g_free (base);
183       g_free (dir);
184       g_free (full);
185       return NULL;
186     }
187
188     /* we know no path was specified, so search standard path for binary */
189     g_free (full);
190     full = g_find_program_in_path (base);
191     if (!full)
192     {
193       g_warning ("This should not happen, %s not in standard path.", base);
194       g_free (base);
195       g_free (dir);
196       return NULL;
197     }
198   }
199
200   g_free (base);
201   g_free (dir);
202   dir = g_path_get_dirname (full);
203   g_free (full);
204
205   return dir;
206 }
207
208 /* Search the given directory for candidate binaries matching the base binary.
209  * Return a GHashTable of major/minor -> directory pairs
210  */
211 GHashTable *
212 get_candidates (const gchar * dir, const gchar * base)
213 {
214   GDir *gdir;
215   GError *error = NULL;
216   const gchar *entry;
217   gchar *path;
218   gchar *suffix;
219
220   gchar *pattern;
221   GPatternSpec *spec;
222
223
224   gchar *test;
225
226   gchar **dirs;
227   gchar **cur;
228
229   GHashTable *candidates = NULL;
230
231   candidates = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
232
233   /* compile our pattern spec */
234   pattern = g_strdup_printf ("%s-*.*", base);
235   spec = g_pattern_spec_new (pattern);
236   g_free (pattern);
237
238   /* get all dirs from the path and prepend with given dir */
239   path = g_strdup_printf ("%s%c%s",
240                           dir, G_SEARCHPATH_SEPARATOR, g_getenv ("PATH"));
241   dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0);
242   g_free (path);
243
244   /* check all of these in reverse order by winding to bottom and going up  */
245   cur = &dirs[0];
246   while (*cur) ++cur;
247
248   while (cur != &dirs[0])
249   {
250     --cur;
251     if (! g_file_test (*cur, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
252       continue;
253
254     gdir = g_dir_open (*cur, 0, &error);
255     if (! gdir)
256     {
257       g_warning ("Could not open dir %s: %s", *cur, error->message);
258       g_error_free (error);
259       return NULL;
260     }
261     while ((entry = g_dir_read_name (gdir)))
262     {
263       if (g_pattern_match_string (spec, entry))
264       {
265         gchar *full;
266
267         /* is it executable ? */
268         full = g_build_filename (*cur, entry, NULL);
269         if (! g_file_test (full, G_FILE_TEST_IS_EXECUTABLE))
270         {
271           g_free (full);
272           continue;
273         }
274         g_free (full);
275
276         /* strip base and dash from it */
277         suffix = g_strdup (&(entry[strlen (base) + 1]));
278
279         /* stricter pattern check: check if it only contains digits or dots */
280         test = g_strdup (suffix);
281         g_strcanon (test, "0123456789.", 'X');
282         if (strstr (test, "X"))
283         {
284           g_free (test);
285           continue;
286         }
287         g_free (test);
288         g_hash_table_insert (candidates, suffix, g_strdup (*cur));
289       }
290     }
291   }
292
293   g_strfreev (dirs);
294   g_pattern_spec_free (spec);
295   return candidates;
296 }
297
298 int main
299 (int argc, char **argv)
300 {
301   GHashTable *candidates;
302   gchar *dir;
303   gchar *base;
304   gchar *highest = NULL;
305   gchar *binary;                /* actual binary we're going to run */
306   gchar *path = NULL;           /* and its path */
307   poptContext ctx;
308   int nextopt;
309
310   /* parse command line options */
311   ctx = poptGetContext ("gst-run", argc, (const char **) argv, options, 0);
312   poptReadDefaultConfig (ctx, TRUE);
313   while ((nextopt = poptGetNextOpt (ctx)) > 0)
314     /* keep looping to parse */;
315
316   argc = poptStrippedArgv (ctx, argc, argv);
317   argv[argc] = NULL;
318   poptFreeContext (ctx);
319
320     /* detect stuff */
321   dir = get_dir_of_binary (argv[0]);
322   base = g_path_get_basename (argv[0]);
323
324   /* unmangle libtool if necessary */
325   unmangle_libtool (&dir, &base);
326
327   /* get all candidate binaries */
328   candidates = get_candidates (dir, base);
329   g_free (dir);
330
331   if (_arg_mm)
332   {
333     /* if a version was forced, look it up in the hash table */
334     dir = g_hash_table_lookup (candidates, _arg_mm);
335     if (!dir)
336     {
337       g_print ("ERROR: Major/minor %s of tool %s not found.\n", _arg_mm, base);
338       return 1;
339     }
340     binary = g_strdup_printf ("%s-%s", base, _arg_mm);
341   }
342   else
343   {
344     /* otherwise, just look up the highest version */
345     g_hash_table_foreach (candidates, (GHFunc) find_highest_version,
346                           &highest);
347     dir = g_hash_table_lookup (candidates, highest);
348     if (!dir)
349     {
350       g_print ("ERROR: No version of tool %s not found.\n", base);
351       return 1;
352     }
353     binary = g_strdup_printf ("%s-%s", base, highest);
354   }
355
356   g_free (base);
357
358   path = g_build_filename (dir, binary, NULL);
359   g_free (binary);
360
361   /* print out list of major/minors we found if asked for */
362   /* FIXME: do them in order by creating a GList of keys and sort them */
363   if (_arg_list_mm)
364   {
365     g_hash_table_foreach (candidates, (GHFunc) hash_print_key, NULL);
366     g_hash_table_destroy (candidates);
367     return 0;
368   }
369
370   /* print out command line if asked for */
371   argv[0] = path;
372   if (_print)
373   {
374     int i;
375     for (i = 0; i < argc; ++i)
376     {
377       g_print ("%s", argv[i]);
378       if (i < argc - 1) g_print (" ");
379     }
380     g_print ("\n");
381   }
382
383   /* execute */
384   if (execv (path, argv) == -1)
385   {
386     g_warning ("Error executing %s: %s (%d)", path, g_strerror (errno), errno);
387   }
388   g_free (path);
389
390   return 0;
391 }