OSDN Git Service

Support options set by preferences assigned within profile.xml
[mingw/mingw-get.git] / src / clistub.c
1 /*
2  * clistub.c
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2009, 2010, 2011, 2012, MinGW Project
8  *
9  *
10  * Initiation stub for command line invocation of mingw-get
11  *
12  *
13  * This is free software.  Permission is granted to copy, modify and
14  * redistribute this software, under the provisions of the GNU General
15  * Public License, Version 3, (or, at your option, any later version),
16  * as published by the Free Software Foundation; see the file COPYING
17  * for licensing details.
18  *
19  * Note, in particular, that this software is provided "as is", in the
20  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
21  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
22  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
23  * MinGW Project, accept liability for any damages, however caused,
24  * arising from the use of this software.
25  *
26  */
27 #define _WIN32_WINNT 0x500
28 #define WIN32_LEAN_AND_MEAN
29
30 #include "debug.h"
31
32 #include <windows.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <libgen.h>
36 #include <process.h>
37 #include <getopt.h>
38
39 #include "pkgopts.h"
40
41 #define EXIT_FATAL  EXIT_FAILURE + 1
42
43 static const char *progname;
44
45 wchar_t *AppPathNameW( const wchar_t *relpath )
46 {
47   /* UTF-16LE implementation; NOT thread safe...
48    *
49    * Map "relpath" into the file system hierarchy with logical root
50    * at the prefix where the application suite is installed, such that
51    * it becomes "d:\prefix\relpath".
52    *
53    * If the application's executables are installed in a directory called
54    * "bin" or "sbin", then this final directory is excluded from the mapped
55    * prefix, (i.e. a program installed as "d:\prefix\bin\prog.exe" returns
56    * the mapped path as "d:\prefix\relpath", rather than as the inclusive
57    * path "d:\prefix\bin\relpath"); in any other case, the prefix is taken
58    * as the path to the directory in which the program file is installed,
59    * (i.e. a program installed as "d:\prefix\foo\prog.exe" returns the
60    * mapped path as "d:\prefix\foo\relpath".
61    *
62    * Mapped path is returned in this static buffer...
63    */
64   static wchar_t retpath[MAX_PATH], *tail = NULL;
65
66   if( tail == NULL )
67   {
68     /* First time initialisation...
69      *
70      * Fill in the static local buffer, with the appropriate "prefix"
71      * string, marking the point at which "relpath" is to be appended.
72      *
73      * On subsequent calls, we reuse this static buffer, leaving the
74      * "prefix" element unchanged, but simply overwriting the "relpath"
75      * element from any prior call; this is NOT thread safe!
76      */
77     wchar_t prev = L'\\';
78     wchar_t bindir[] = L"bin", *bindir_p = bindir;
79     wchar_t *mark, *scan = mark = tail = retpath;
80
81     /* Ascertain the installation path of the calling executable.
82      */
83     int chk = GetModuleFileNameW( NULL, retpath, MAX_PATH );
84
85     /* Validate it; reject any result which doesn't fit in "retpath".
86      */
87     if( (chk == 0) || ((chk == MAX_PATH) && (retpath[--chk] != L'\0')) )
88       return tail = NULL;
89
90     /* Parse it, to locate the end of the effective "prefix" string...
91      */
92     do { if( *scan == L'/' )
93            /*
94             * This is a sanity check; it should not be necessary, but...
95             *
96             * Enforce use of "\" rather than "/" as path component separator;
97             * ( "LoadLibrary" may be broken, since it seems to care! )
98             */
99            *scan = L'\\';
100
101          if( *scan && (prev == L'\\') )
102          {
103            /* We found the start a new path name component directory,
104             * (or maybe the file name, at the end); mark it as a possible
105             * final element of the path name, leaving "tail" pointing to
106             * the previously marked element.
107             */
108            tail = mark;
109            mark = scan;
110          }
111        } while( (prev = *scan++) != L'\0' );
112
113     /* When we get to here, "mark" should point to the executable file name,
114      * at the end of the path name string, while "tail" should point to the
115      * last directory in the installation path; we now check, without regard
116      * to case, if this final directory name is "bin" or "sbin"...
117      */
118     if( (*(scan = tail) == L's') || (*scan == L'S') )
119       /*
120        * Might be "sbin"; skip the initial "s", and check for "bin"...
121        */
122       ++scan;
123
124     while( *bindir_p && ((*scan++ | L'\x20') == *bindir_p++) )
125       /*
126        * ...could still match "bin"...
127        */ ;
128     if( *bindir_p || (*scan != L'\\') )
129       /*
130        * No, it didn't match; adjust "tail", so we leave the final
131        * directory name as part of "prefix".
132        */
133       tail = mark;
134   }
135
136   if( relpath == NULL )
137     /*
138      * No "relpath" argument given; simply truncate, to return only
139      * the effective "prefix" string...
140      */
141     *tail = L'\0';
142
143   else
144   { wchar_t *append = tail;
145     do { /*
146           * Append the specified path to the application's root,
147           * again, taking care to use "\" as the separator character...
148           */
149          *append++ = (*relpath == L'/') ? L'\\' : *relpath;
150        } while( *relpath++ && ((append - retpath) < MAX_PATH) );
151
152     if( *--append != L'\0' )
153       /*
154        * Abort, if we didn't properly terminate the return string.
155        */
156       return NULL;
157   }
158   return retpath;
159 }
160
161 extern const char *version_identification;
162
163 static const char *help_text =
164 "Manage MinGW and MSYS installations (command line user interface).\n\n"
165
166 "Usage:\n"
167 "  mingw-get [OPTIONS] ACTION [package-spec ...]\n\n"
168
169 "  mingw-get update\n"
170 "  mingw-get [OPTIONS] {install | upgrade | remove} package-spec ...\n"
171 "  mingw-get [OPTIONS] {show | list} [package-spec ...]\n\n"
172
173 "Options:\n"
174 "  --help, -h        Show this help text\n"
175 "\n"
176 "  --version, -V     Show version and licence information\n"
177 "\n"
178 "  --verbose, -v     Increase verbosity of diagnostic or\n"
179 "                    progress reporting output; repeat up\n"
180 "                    to three times for maximum verbosity\n"
181 "  --verbose=N       Set verbosity level to N; (0 <= N <= 3)\n"
182 "\n"
183
184 /* The "--trace" option is available only when dynamic tracing
185  * debugging support is compiled in; don't advertise it otherwise.
186  */
187 #if DEBUG_ENABLED( DEBUG_TRACE_DYNAMIC )
188 "  --trace=N         Enable tracing feature N; (debugging aid)\n"
189 "\n"
190 #endif
191
192 /* The following are always available...
193  */
194 "  --reinstall       When performing an install or upgrade\n"
195 "                    operation, reinstall any named package\n"
196 "                    for which the most recent release is\n"
197 "                    already installed\n"
198 "\n"
199 "  --recursive       Extend the scope of \"install --reinstall\"\n"
200 "                    or of \"upgrade\", such that the operation\n"
201 "                    is applied recursively to all prerequisites\n"
202 "                    of all packages named on the command line\n"
203 "\n"
204 "  --download-only   Download the package archive files which\n"
205 "                    would be required to complete the specified\n"
206 "                    install, upgrade, or source operation, but\n"
207 "                    do not unpack them, or otherwise proceed\n"
208 "                    to complete the operation\n"
209 "\n"
210 "  --print-uris      Display the repository URIs from which\n"
211 "                    package archive files would be retrieved\n"
212 "                    prior to performing the specified install,\n"
213 "                    upgrade, or source operation, but do not\n"
214 "                    download any package file, or otherwise\n"
215 "                    proceed with the operation\n"
216 "\n"
217 "  --all-related     When performing source or licence operations,\n"
218 "                    causes mingw-get to retrieve, and optionally to\n"
219 "                    unpack the source or licence archives for all\n"
220 "                    runtime prerequisites of, and in addition to,\n"
221 "                    the nominated package\n"
222 "\n"
223 "  --desktop[=all-users]\n"
224 "                    Enable the creation of desktop shortcuts, for\n"
225 "                    packages which provide the capability via pre-\n"
226 "                    or post-install scripts; the optional 'all-users'\n"
227 "                    qualifier requests that all such shortcuts are\n"
228 "                    to be made available to all users; without it\n"
229 "                    shortcuts will be created for current user only\n"
230 "\n"
231 "                    Note that specification of this option does not\n"
232 "                    guarantee that shortcuts will be created; the\n"
233 "                    onus lies with individual package maintainers\n"
234 "                    to provide scripting to support this capability\n"
235 "\n"
236 "  --start-menu[=all-users]\n"
237 "                    Enable the creation of start menu shortcuts, for\n"
238 "                    packages which provide the capability via pre-\n"
239 "                    or post-install scripts; the optional 'all-users'\n"
240 "                    qualifier requests that all such shortcuts are\n"
241 "                    to be made available to all users; without it\n"
242 "                    shortcuts will be created for current user only\n"
243 "\n"
244 "                    Note that specification of this option does not\n"
245 "                    guarantee that shortcuts will be created; the\n"
246 "                    onus lies with individual package maintainers\n"
247 "                    to provide scripting to support this capability\n"
248 "\n"
249 "Actions:\n"
250 "  update            Update local copy of repository catalogues\n"
251 "  list, show        List and show details of available packages\n"
252 "  source            Download and optionally unpack package sources\n"
253 "  licence           Download and optionally unpack licence packages,\n"
254 "                    handling them as if they are source packages\n"
255 "  install           Install new packages\n"
256 "  upgrade           Upgrade previously installed packages\n"
257 "  remove            Remove previously installed packages\n\n"
258
259 "Package Specifications:\n"
260 "  [subsystem-]name[-component]:\n"
261 "  msys-bash-doc     The 'doc' component of the bash package for MSYS\n"
262 "  mingw32-gdb       All components of the gdb package for MinGW\n\n"
263
264 "Use 'mingw-get list' to identify possible package names\n"
265 "and the components associated with each.\n\n";
266
267 #define  IMPLEMENT_INITIATION_RITES     PHASE_ONE_RITES
268 #include "rites.c"
269
270 static __inline__ __attribute__((__always_inline__))
271 char **cli_setargv( HMODULE my_dll, struct pkgopts *opts, char **argv )
272 {
273   /* A local wrapper function to facilitate passing pre-parsed
274    * command line options while performing the "climain()" call
275    * into mingw-get-0.dll
276    *
277    * Note that this requires a version of mingw-get-0.dll which
278    * provides the "cli_setopts()" hook...
279    */
280   typedef void (*dll_hook)(struct pkgopts *);
281   dll_hook cli_setopts = (dll_hook)(GetProcAddress( my_dll, "cli_setopts" ));
282   if( cli_setopts != NULL )
283     /*
284      * ...which allows us to pass the pre-parsed options.
285      */
286     cli_setopts( opts );
287
288   /* In any case, we always return the argument vector which is
289    * to be passed in the "climain()" call itself.
290    */
291   return argv;
292 }
293
294 /* FIXME: We may ultimately choose to factor the following atoi() replacement
295  * into a separate, free-standing module.  For now we keep it here, as a static
296  * function, but we keep the required #include adjacent.
297  */
298 #include <ctype.h>
299
300 static int xatoi( const char *input )
301 {
302   /* A replacement for the standard atoi() function; this implementation
303    * supports conversion of octal or hexadecimal representations, in addition
304    * to the decimal representation required by standard atoi().
305    *
306    * We begin by initialising the result accumulator to zero...
307    */
308   int result = 0;
309
310   /* ...then, provided we have an input string to interpret...
311    */
312   if( input != NULL )
313   {
314     /* ...we proceed with interpretation and accumulation of the result,
315      * noting that we may have to handle an initial minus sign, (but we
316      * don't know yet, so assume that not for now).
317      */
318     int negate = 0;
319     while( isspace( *input ) )
320       /*
321        * Ignore leading white space.
322        */
323       ++input;
324
325     if( *input == '-' )
326       /*
327        * An initial minus sign requires negation
328        * of the accumulated result...
329        */
330       negate = *input++;
331
332     else if( *input == '+' )
333       /*
334        * ...while an initial plus sign is redundant,
335        * and may simply be ignored.
336        */
337       ++input;
338
339     if( *input == '0' )
340     {
341       /* An initial zero signifies either hexadecimal
342        * or octal representation...
343        */
344       if( (*++input | '\x20') == 'x' )
345         /*
346          * ...with following 'x' or 'X' indicating
347          * the hexadecimal case.
348          */
349         while( isxdigit( *++input ) )
350         {
351           /* Interpret sequence of hexadecimal digits...
352            */
353           result = (result << 4) + *input;
354           if( *input > 'F' )
355             /*
356              * ...with ASCII to binary conversion for
357              * lower case digits 'a'..'f',...
358              */
359             result -= 'a' - 10;
360
361           else if( *input > '9' )
362             /*
363              * ...ASCII to binary conversion for
364              * upper case digits 'A'..'F',...
365              */
366             result -= 'A' - 10;
367
368           else
369             /* ...or ASCII to decimal conversion,
370              * as appropriate.
371              */
372             result -= '0';
373         }
374       else while( (*input >= '0') && (*input < '8') )
375         /*
376          * Interpret sequence of octal digits...
377          */
378         result = (result << 3) + *input++ - '0';
379     }
380
381     else while( isdigit( *input ) )
382       /*
383        * Interpret sequence of decimal digits...
384        */
385       result = (result << 1) + (result << 3) + *input++ - '0';
386
387     if( negate )
388       /*
389        * We had an initial minus sign, so we must
390        * return the negated result...
391        */
392       return (0 - result);
393   }
394   /* ...otherwise, we simply return the accumulated result
395    */
396   return result;
397 }
398
399 #define atmost( lim, val )              ((lim) < (val)) ? (lim) : (val)
400
401 int main( int argc, char **argv )
402 {
403   /* Provide storage for interpretation of any parsed command line options.
404    * Note that we could also initialise them here, but then we would need to
405    * give attention to the number of initialisers required; to save us that
406    * concern we will defer to an initialisation loop, below.
407    */
408   struct pkgopts parsed_options;
409
410   /* Make a note of this program's name, and where it's installed.
411    */
412   wchar_t *approot;
413   progname = basename( *argv );
414
415   if( argc > 1 )
416   {
417     /* The user specified arguments on the command line...
418      * Interpret any which specify processing options for this application,
419      * (these are all specified in GNU `long only' style).
420      */
421     int optref;
422     struct option options[] =
423     {
424       /* Option Name      Argument Category    Store To   Return Value
425        * --------------   ------------------   --------   ------------------
426        */
427       { "version",        no_argument,         NULL,      'V'                },
428       { "help",           no_argument,         NULL,      'h'                },
429       { "verbose",        optional_argument,   NULL,      OPTION_VERBOSE     },
430
431       { "recursive",      no_argument,         &optref,   OPTION_RECURSIVE   },
432       { "reinstall",      no_argument,         &optref,   OPTION_REINSTALL   },
433       { "download-only",  no_argument,         &optref,   OPTION_DNLOAD_ONLY },
434       { "print-uris",     no_argument,         &optref,   OPTION_PRINT_URIS  },
435
436       { "all-related",    no_argument,         &optref,   OPTION_ALL_RELATED },
437
438       { "desktop",        optional_argument,   &optref,   OPTION_DESKTOP     },
439       { "start-menu",     optional_argument,   &optref,   OPTION_START_MENU  },
440
441 #     if DEBUG_ENABLED( DEBUG_TRACE_DYNAMIC )
442         /* The "--trace" option is supported only when dynamic tracing
443          * debugging support has been compiled in.
444          */
445       { "trace",          required_argument,   &optref,   OPTION_TRACE       },
446 #     endif
447
448       /* This list must be terminated by a null definition...
449        */
450       { NULL, 0, NULL, 0 }
451     };
452
453     int opt, offset;
454     for( opt = OPTION_FLAGS; opt < OPTION_TABLE_SIZE; ++opt )
455       /*
456        * Ensure that all entries within the options table are initialised
457        * to zero, (equivalent to NULL for pointer entries)...
458        */
459       parsed_options.flags[opt].numeric = 0;
460
461     while( (opt = getopt_long_only( argc, argv, "vVh", options, &offset )) != -1 )
462       /*
463        * Parse any user specified options from the command line...
464        */
465       switch( opt )
466       {
467         case 'V':
468           /* This is a request to display the version of the application;
469            * emit the requisite informational message, and quit.
470            */
471           printf( version_identification );
472           return EXIT_SUCCESS;
473
474         case 'h':
475           /* This is a request to display help text and quit.
476            */
477           printf( help_text );
478           return EXIT_SUCCESS;
479
480         case OPTION_VERBOSE:
481           /* This is a request to set an explicit verbosity level,
482            * (minimum zero, maximum three), or if no explicit argument
483            * is specified, to increment verbosity as does "-v".
484            */
485           if( optarg != NULL )
486           {
487             /* This is the case where an explicit level was specified...
488              */
489             parsed_options.flags[OPTION_FLAGS].numeric =
490               (parsed_options.flags[OPTION_FLAGS].numeric & ~OPTION_VERBOSE)
491               | atmost( OPTION_VERBOSE_MAX, xatoi( optarg ));
492             break;
493           }
494
495         case 'v':
496           /* This is a request to increment the verbosity level
497            * from its initial zero setting, to a maximum of three.
498            */
499           if( (parsed_options.flags[OPTION_FLAGS].numeric & OPTION_VERBOSE) < 3 )
500             ++parsed_options.flags[OPTION_FLAGS].numeric;
501           break;
502
503         case OPTION_GENERIC:
504           switch( optref & OPTION_STORAGE_CLASS )
505           {
506             /* This represents a generic option specification,
507              * allowing for storage of a option argument of the
508              * specified class into a specified option slot...
509              */
510             unsigned shift;
511
512             case OPTION_STORE_STRING:
513               /* This is a simple store of a option argument
514                * which represents a character string.
515                */
516               mark_option_as_set( parsed_options, optref );
517               parsed_options.flags[optref & 0xfff].string = optarg;
518               break;
519
520             case OPTION_STORE_NUMBER:
521               /* This is also a simple store of the argument value,
522                * in this case interpreted as a number.
523                */
524               mark_option_as_set( parsed_options, optref );
525               parsed_options.flags[optref & 0xfff].numeric = xatoi( optarg );
526               break;
527
528             case OPTION_MERGE_NUMBER:
529               /* In this case, we combine the value of the argument,
530                * again interpreted as a number, with the original value
531                * stored in the option slot, forming the bitwise logical
532                * .OR. of the pair of values.
533                */
534               mark_option_as_set( parsed_options, optref );
535               parsed_options.flags[optref & 0xfff].numeric |= xatoi( optarg );
536               break;
537
538             default:
539               /* This is a mask and store operation for a specified
540                * bit-field within the first pair of flags slots; in
541                * this case, the optref value itself specifies a 12-bit
542                * value, a 12-bit combining mask, and an alignment shift
543                * count between 0 and 52, in 4-bit increments.
544                */
545               if( (shift = (optref & OPTION_SHIFT_MASK) >> 22) < 53 )
546               {
547                 uint64_t value = optref & 0xfff;
548                 uint64_t mask = (optref & 0xfff000) >> 12;
549                 *(uint64_t *)(parsed_options.flags) &= ~(mask << shift);
550                 *(uint64_t *)(parsed_options.flags) |= value << shift;
551               }
552           }
553           break;
554
555         default:
556           /* User specified an invalid or unsupported option...
557            */
558           if( opt != '?' )
559             fprintf( stderr, "%s: option '-%s' not yet supported\n",
560                 progname, options[offset].name
561               );
562           return EXIT_FAILURE;
563       }
564   }
565
566   /* Establish the installation path for the mingw-get application...
567    */
568   if( (approot = AppPathNameW( NULL )) != NULL )
569   {
570     /* ...and set up the APPROOT environment variable to refer to
571      * the associated installation prefix...
572      */
573     char approot_setup[1 + snprintf( NULL, 0, "APPROOT=%S", approot )];
574     snprintf( approot_setup, sizeof( approot_setup ), "APPROOT=%S", approot );
575     putenv( approot_setup );
576   }
577
578   if( argc > 1 )
579   {
580     /* The user specified arguments on the command line...
581      * we load the supporting DLL into the current process context,
582      * then, remaining in command line mode, we jump to its main
583      * command line processing routine...
584      */
585     int lock;
586     char *argv_base = *argv;
587     typedef int (*dll_entry)( int, char ** );
588     HMODULE my_dll = LoadLibraryW( AppPathNameW( MINGW_GET_DLL ) );
589     dll_entry climain = (dll_entry)(GetProcAddress( my_dll, "climain" ));
590     if( climain == NULL )
591     {
592       /* ...bailing out, on failure to load the DLL.
593        */
594       fprintf( stderr, "%s: %S: shared library load failed\n", 
595           progname, MINGW_GET_DLL
596         );
597       return EXIT_FATAL;
598     }
599
600     /* Adjust argc and argv to discount parsed options...
601      */
602     argc -= optind;
603     argv += optind - 1;
604     /*
605      * ...while preserving the original argv[0] reference within
606      * the first remaining argument to be passed to climain().
607      */
608     *argv = argv_base;
609
610     /* We want only one mingw-get process accessing the XML database
611      * at any time; attempt to acquire an exclusive access lock...
612      */
613     if( (lock = pkgInitRites( progname )) >= 0 )
614     {
615       /* ...and proceed, only if successful.
616        *  A non-zero return value indicates that a fatal error occurred.
617        */
618       int rc = climain( argc, cli_setargv( my_dll, &parsed_options, argv ) );
619
620       /* We must release the mingw-get DLL code, BEFORE we invoke
621        * last rites processing, (otherwise the last rites clean-up
622        * handler exhibits abnormal behaviour when it is exec'd).
623        */
624       FreeLibrary( my_dll );
625       if (rc == 0)
626         return pkgLastRites( lock, progname );
627       else
628       {
629         (void) pkgLastRites( lock, progname );
630         return EXIT_FATAL;
631       }
632     }
633     /* If we get to here, then we failed to acquire a lock;
634      * we MUST abort!
635      */
636     return EXIT_FATAL;
637   }
638
639   else
640   { /* No arguments were specified on the command line...
641      * we interpret this as a request to start up in GUI mode...
642      */ 
643     wchar_t *libexec_path = AppPathNameW( MINGW_GET_GUI );
644     char gui_program[1 + snprintf( NULL, 0, "%S", libexec_path )];
645     snprintf( gui_program, sizeof( gui_program ), "%S", libexec_path );
646     int status = execv( gui_program, (const char* const*)(argv) );
647
648     /* If we get to here, then the GUI could not be started...
649      * Issue a diagnostic message, before abnormal termination.
650      */
651     fprintf( stderr,
652         "%s: %S: unable to start GUI; helper program not installed\n",
653         progname, MINGW_GET_GUI
654       );
655     return EXIT_FATAL;
656   }
657 }
658
659 /* $RCSfile$: end of file */