tools/gst-launch.1.in: Give example for network streaming (#351998)
[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 <glib.h>
31
32 /* global statics for option parsing */
33 static gboolean _print = FALSE;
34 static gchar *_arg_mm = NULL;
35 static gboolean _arg_list_mm = FALSE;
36
37 /* popt options table for the wrapper */
38 static GOptionEntry wrapper_options[] = {
39   {"print", 'p', 0, G_OPTION_ARG_NONE, &_print,
40       "print wrapped command line options", NULL},
41   {"gst-mm", 0, 0, G_OPTION_ARG_STRING, &_arg_mm,
42       "Force major/minor version", "VERSION"},
43   {"gst-list-mm", 0, 0, G_OPTION_ARG_NONE, &_arg_list_mm,
44       "List found major/minor versions", NULL},
45   {NULL}
46 };
47
48 /* print out the major/minor, which is the hash key */
49 static void
50 hash_print_key (gchar * key, gchar * value)
51 {
52   g_print ("%s\n", (gchar *) key);
53 }
54
55 /* return value like strcmp, but compares major/minor numerically */
56 static gint
57 compare_major_minor (const gchar * first, const gchar * second)
58 {
59   gchar **firsts, **seconds;
60   gint fmaj, fmin, smaj, smin;
61   gint ret = 0;
62
63   firsts = g_strsplit (first, ".", 0);
64   seconds = g_strsplit (second, ".", 0);
65
66   if (firsts[0] == NULL || firsts[1] == NULL) {
67     ret = -1;
68     goto beach;
69   }
70   if (seconds[0] == NULL || seconds[1] == NULL) {
71     ret = 1;
72     goto beach;
73   }
74
75   fmaj = atoi (firsts[0]);
76   fmin = atoi (firsts[1]);
77   smaj = atoi (seconds[0]);
78   smin = atoi (seconds[1]);
79
80   if (fmaj < smaj) {
81     ret = -1;
82     goto beach;
83   }
84   if (fmaj > smaj) {
85     ret = 1;
86     goto beach;
87   }
88
89   /* fmaj == smaj */
90   if (fmin < smin) {
91     ret = -1;
92     goto beach;
93   }
94   if (fmin > smin) {
95     ret = 1;
96     goto beach;
97   }
98   ret = 0;
99
100 beach:
101   g_strfreev (firsts);
102   g_strfreev (seconds);
103   return ret;
104 }
105
106 static void
107 find_highest_version (gchar * key, gchar * value, gchar ** highest)
108 {
109   if (*highest == NULL) {
110     /* first value, so just set it */
111     *highest = key;
112   }
113   if (compare_major_minor (key, *highest) > 0)
114     *highest = key;
115 }
116
117 /* Libtool creates shell scripts named "base" that calls actual binaries as
118  * .libs/lt-base.  If we detect this is a libtool script, unmangle so we
119  * find the right binaries */
120 static void
121 unmangle_libtool (gchar ** dir, gchar ** base)
122 {
123   gchar *new_dir, *new_base;
124
125   if (!*dir)
126     return;
127   if (!*base)
128     return;
129
130   /* we assume libtool when base starts with lt- and dir ends with .libs */
131   if (!g_str_has_prefix (*base, "lt-"))
132     return;
133   if (!g_str_has_suffix (*dir, ".libs"))
134     return;
135
136   new_base = g_strdup (&((*base)[3]));
137   new_dir = g_path_get_dirname (*dir);
138   g_free (*base);
139   g_free (*dir);
140   *base = new_base;
141   *dir = new_dir;
142 }
143
144 /* Returns a directory path that contains the binary given as an argument.
145  * If the binary given contains a path, it gets looked for in that path.
146  * If it doesn't contain a path, it gets looked for in the standard path.
147  *
148  * The returned string is newly allocated.
149  */
150 gchar *
151 get_dir_of_binary (const gchar * binary)
152 {
153   gchar *base, *dir;
154   gchar *full;
155
156   base = g_path_get_basename (binary);
157   dir = g_path_get_dirname (binary);
158
159   /* if putting these two together yields the same as binary,
160    * then we have the right breakup.  If not, it's because no path was
161    * specified which caused get_basename to return "." */
162   full = g_build_filename (dir, base, NULL);
163
164   if (strcmp (full, binary) != 0) {
165     if (strcmp (dir, ".") != 0) {
166       g_warning ("This should not happen, g_path_get_dirname () has changed.");
167       g_free (base);
168       g_free (dir);
169       g_free (full);
170       return NULL;
171     }
172
173     /* we know no path was specified, so search standard path for binary */
174     g_free (full);
175     full = g_find_program_in_path (base);
176     if (!full) {
177       g_warning ("This should not happen, %s not in standard path.", base);
178       g_free (base);
179       g_free (dir);
180       return NULL;
181     }
182   }
183
184   g_free (base);
185   g_free (dir);
186   dir = g_path_get_dirname (full);
187   g_free (full);
188
189   return dir;
190 }
191
192 /* Search the given directory for candidate binaries matching the base binary.
193  * Return a GHashTable of major/minor -> directory pairs
194  */
195 GHashTable *
196 get_candidates (const gchar * dir, const gchar * base)
197 {
198   GDir *gdir;
199   GError *error = NULL;
200   const gchar *entry;
201   gchar *path;
202   gchar *suffix, *copy;
203
204   gchar *pattern;
205   GPatternSpec *spec, *specexe;
206
207   gchar **dirs;
208   gchar **cur;
209
210   GHashTable *candidates = NULL;
211
212   candidates = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
213
214   /* compile our pattern specs */
215   pattern = g_strdup_printf ("%s-*.*", base);
216   spec = g_pattern_spec_new (pattern);
217   g_free (pattern);
218   pattern = g_strdup_printf ("%s-*.*.exe", base);
219   specexe = g_pattern_spec_new (pattern);
220   g_free (pattern);
221
222   /* get all dirs from the path and prepend with given dir */
223   path = g_strdup_printf ("%s%c%s",
224       dir, G_SEARCHPATH_SEPARATOR, g_getenv ("PATH"));
225   dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0);
226   g_free (path);
227
228   /* check all of these in reverse order by winding to bottom and going up  */
229   cur = &dirs[0];
230   while (*cur)
231     ++cur;
232
233   while (cur != &dirs[0]) {
234     --cur;
235     if (!g_file_test (*cur, G_FILE_TEST_EXISTS) ||
236         !g_file_test (*cur, G_FILE_TEST_IS_DIR)) {
237       continue;
238     }
239
240     gdir = g_dir_open (*cur, 0, &error);
241     if (!gdir) {
242       g_warning ("Could not open dir %s: %s", *cur, error->message);
243       g_error_free (error);
244       return NULL;
245     }
246     while ((entry = g_dir_read_name (gdir))) {
247       if (g_pattern_match_string (spec, entry)
248           || g_pattern_match_string (specexe, entry)) {
249         gchar *full;
250
251         /* is it executable ? */
252         full = g_build_filename (*cur, entry, NULL);
253         if (!g_file_test (full, G_FILE_TEST_IS_EXECUTABLE)) {
254           g_free (full);
255           continue;
256         }
257         g_free (full);
258
259         /* strip base and dash from it */
260         suffix = g_strdup (&(entry[strlen (base) + 1]));
261         copy = g_strdup (suffix);
262
263         /* strip possible .exe from copy */
264         if (g_strrstr (copy, ".exe"))
265           g_strrstr (copy, ".exe")[0] = '\0';
266
267         /* stricter pattern check: check if it only contains digits or dots */
268         g_strcanon (copy, "0123456789.", 'X');
269         if (strstr (copy, "X")) {
270           g_free (suffix);
271           g_free (copy);
272           continue;
273         }
274         g_free (copy);
275         g_hash_table_insert (candidates, suffix, g_strdup (*cur));
276       }
277     }
278   }
279
280   g_strfreev (dirs);
281   g_pattern_spec_free (spec);
282   return candidates;
283 }
284
285 int
286 main (int argc, char **argv)
287 {
288   GHashTable *candidates;
289   gchar *dir;
290   gchar *base;
291   gchar *highest = NULL;
292   gchar *binary;                /* actual binary we're going to run */
293   gchar *path = NULL;           /* and its path */
294   gchar *desc;
295   GOptionContext *ctx;
296   GError *err = NULL;
297
298   /* detect stuff */
299   dir = get_dir_of_binary (argv[0]);
300   base = g_path_get_basename (argv[0]);
301
302   /* parse command line options */
303   desc = g_strdup_printf ("wrapper to call versioned %s", base);
304   ctx = g_option_context_new (desc);
305   g_free (desc);
306   g_option_context_set_ignore_unknown_options (ctx, TRUE);
307   g_option_context_add_main_entries (ctx, wrapper_options, GETTEXT_PACKAGE);
308   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
309     g_print ("Error initializing: %s\n", err->message);
310     exit (1);
311   }
312   g_option_context_free (ctx);
313
314   /* unmangle libtool if necessary */
315   unmangle_libtool (&dir, &base);
316
317   /* get all candidate binaries */
318   candidates = get_candidates (dir, base);
319   g_free (dir);
320
321   if (_arg_mm) {
322     /* if a version was forced, look it up in the hash table */
323     dir = g_hash_table_lookup (candidates, _arg_mm);
324     if (!dir) {
325       g_print ("ERROR: Major/minor %s of tool %s not found.\n", _arg_mm, base);
326       return 1;
327     }
328     binary = g_strdup_printf ("%s-%s", base, _arg_mm);
329   } else {
330     highest = NULL;
331
332     /* otherwise, just look up the highest version */
333     if (candidates) {
334       g_hash_table_foreach (candidates, (GHFunc) find_highest_version,
335           &highest);
336     }
337
338     if (highest == NULL) {
339       g_print ("ERROR: No version of tool %s found.\n", base);
340       return 1;
341     }
342     dir = g_hash_table_lookup (candidates, highest);
343     binary = g_strdup_printf ("%s-%s", base, highest);
344   }
345
346   g_free (base);
347
348   path = g_build_filename (dir, binary, NULL);
349   g_free (binary);
350
351   /* print out list of major/minors we found if asked for */
352   /* FIXME: do them in order by creating a GList of keys and sort them */
353   if (_arg_list_mm) {
354     g_hash_table_foreach (candidates, (GHFunc) hash_print_key, NULL);
355     g_hash_table_destroy (candidates);
356     return 0;
357   }
358
359   /* print out command line if asked for */
360   argv[0] = path;
361   if (_print) {
362     int i;
363
364     for (i = 0; i < argc; ++i) {
365       g_print ("%s", argv[i]);
366       if (i < argc - 1)
367         g_print (" ");
368     }
369     g_print ("\n");
370   }
371
372   /* execute */
373   if (execv (path, argv) == -1) {
374     g_warning ("Error executing %s: %s (%d)", path, g_strerror (errno), errno);
375   }
376   g_free (path);
377
378   return 0;
379 }