OSDN Git Service

Discontinue formal use of the build-aux submodule.
[mingw/mingw-get.git] / src / mkpath.c
1 /*
2  * mkpath.c
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2009, 2011, 2013, MinGW.org Project
8  *
9  *
10  * Helper functions for constructing path names, creating directory
11  * hierarchies, and preparing to write new files within any specified
12  * file system hierarchy.
13  *
14  *
15  * This is free software.  Permission is granted to copy, modify and
16  * redistribute this software, under the provisions of the GNU General
17  * Public License, Version 3, (or, at your option, any later version),
18  * as published by the Free Software Foundation; see the file COPYING
19  * for licensing details.
20  *
21  * Note, in particular, that this software is provided "as is", in the
22  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
23  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
24  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
25  * MinGW Project, accept liability for any damages, however caused,
26  * arising from the use of this software.
27  *
28  */
29 #include "mkpath.h"
30 #include "pkgimpl.h"
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38 #include <fcntl.h>
39 #include <errno.h>
40
41 #ifdef _WIN32
42  /*
43   * MS-Windows nuisances...
44   * mkdir() function doesn't accept a `mode' argument; ignore it.
45   */
46 # define mkdir( PATH, MODE )  _mkdir( PATH )
47  /*
48   * MS-Windows _O_BINARY vs. _O_TEXT discrimination can't be explicitly
49   * resolved in a simple `creat()' call; instead, we will use `_open()',
50   * with one of the following explicit attribute sets...
51   */
52 # if IMPLEMENTATION_LEVEL == SETUP_TOOL_COMPONENT
53   /*
54    * ...allowing overwrite in the setup tool implementation...
55    */
56 #  define _O_NEWFILE  _O_RDWR | _O_CREAT | _O_TRUNC | _O_BINARY
57 # else
58   /* ...but not in the standard mingw-get implementation...
59    */
60 #  define _O_NEWFILE  _O_RDWR | _O_CREAT | _O_EXCL | _O_BINARY
61 # endif
62 # define creat(P,M)  _open( P, _O_NEWFILE, _map_posix_mode(M) )
63
64  /* Furthermore, MS-Windows protection modes are naive, in comparison
65   * to the POSIX modes specified within tar archive headers; firstly,
66   * there is no concept of `execute' permissions; secondly, there is
67   * no concept of a `write-only' file, (thus _S_IREAD permission is
68   * implicitly granted for all files); thirdly, `write' permission is
69   * granted only by a single _S_IWRITE flag, so at best, we must set
70   * this if any of POSIX's S_IWUSR, S_IWGRP or S_IWOTH flags are set
71   * in the tar header; finally, MS-Windows has no counterpart for any
72   * of POSIX's `suid', `sgid' or `sticky' bits.
73   */
74 # define _S_IWANY  0222  /* eqv. POSIX's S_IWUSR | S_IWGRP | S_IWOTH */
75 # define _map_posix_mode(M)  _S_IREAD | (((M) & _S_IWANY) ? _S_IWRITE : 0)
76
77 #endif
78
79 const char *pkgArchivePath()
80 {
81   /* Specify where downloaded packages are cached,
82    * within the local file system.
83    */
84   return "%R" "var/cache/mingw-get/packages" "%/M/%F";
85 }
86
87 const char *pkgSourceArchivePath()
88 {
89   /* Specify where downloaded source packages are cached,
90    * within the local file system.
91    */
92   return "%R" "var/cache/mingw-get/source" "%/M/%F";
93 }
94
95 int mkpath( char *buf, const char *fmt, const char *file, const char *modifier )
96 {
97   /* A helper function, for constructing package URL strings.
98    * Return value is the length, in bytes, of the constructed string,
99    * which is returned in "buf"; call with "buf = NULL" to determine
100    * the size of buffer required, without storing the string.
101    *
102    * Constructed URL is copied from "fmt", with...
103    *
104    *   %%     replaced by a single literal "%" character;
105    *   %[/]F  replaced by the string passed as "file";
106    *   %[/]M  replaced by the string passed as "modifier";
107    *   %[/]R  replaced by the "APPROOT" environment string.
108    *
109    * Any other character present in "fmt" is copied literally.  In
110    * the case of "%F", "%M" and "%R", inclusion of the optional "/"
111    * flag causes a single "/" character to be inserted before the
112    * substitute string, provided this is not NULL; ("\\" may be
113    * used, but is not recommended, in place of the "/" flag).
114    */
115   char c;
116   int len = 0;
117
118   /* Scan "fmt"...
119    */
120   do { if( (c = *fmt++) == '%' )
121        {
122          /* Interpret substitution tags...
123           */
124          char flag = *fmt;
125          const char *subst = NULL;
126
127          /* ...checking for presence of a "/" flag...
128           */
129          if( ((flag == '/') || (flag == '\\')) && fmt[1] )
130            /*
131             * ...and, when found, with a possibly valid format spec,
132             * advance to parse that spec...
133             */
134            ++fmt;
135
136          else
137            /* ...establish absence of the flag.
138             */
139            flag = '\0';
140
141          switch( c = *fmt++ )
142          {
143            case 'F':
144              /*
145               * Schedule substitution of text specified as "file".
146               */
147              subst = file;
148              break;
149
150            case 'M':
151              /*
152               * Schedule substitution of text specified as "modifier",
153               */
154              subst = modifier;
155              break;
156
157            case 'R':
158              /*
159               * Schedule substitution from the "APPROOT" environment string,
160               */
161              subst = getenv( "APPROOT" );
162              break;
163
164            case '%':
165              /*
166               * Interpreting "%%", but may have been "%/%", which is invalid...
167               */
168              if( flag == '\0' ) 
169              {
170                /*
171                 * It was just "%%", so store a literal "%" character.
172                 */
173                if( buf != NULL )
174                  *buf++ = '%';
175                ++len;
176                break;
177              }
178
179              /* If we get to here, it was the invalid "%/%" form; backtrack,
180               * and fall through to emit literal "%/", then resume parsing,
181               * treating the second "%" as the possible starting character
182               * of a new format specification.
183               */
184              c = flag;
185              --fmt;
186
187            default:
188              if( buf != NULL )
189              {
190                /* Store the literal "%" character,
191                 * followed by the unrecognised tag character.
192                 */
193                *buf++ = '%';
194                *buf++ = c;
195              }
196              len += 2;
197          }
198
199          if( subst != NULL )
200          {
201            /* Perform scheduled substitution of "file", "modifier"
202             * or the APPROOT environment string...
203             */
204            if( flag )
205            {
206              ++len;
207              if( buf != NULL )
208                *buf++ = flag;
209            }
210            while( *subst )
211            {
212              /* ...counting and copying character by character.
213               */
214              ++len;
215              if( buf != NULL )
216                *buf++ = *subst;
217              ++subst;
218            }
219          }
220        }
221
222        else
223        {
224          /*
225           * Copy one literal character from "fmt"...
226           */
227          if( buf != NULL )
228            /*
229             * ...storing as necessary...
230             */
231            *buf++ = c;
232
233          /* ...and counting it anyway.
234           */
235          ++len;
236        }
237      } while( c );
238
239   /* Always return the total number of characters which were, or would
240    * have been transferred to "buf".
241    */
242   return len;
243 }
244
245 static
246 void create_parent_directory_hierarchy( const char *pathname, int mode )
247 {
248   /* Recursive helper function to create a directory branch, including
249    * all missing parent directories, (analogous to using "mkdir -p").
250    *
251    * FIXME: We allow for either "/" or "\" as the directory separator;
252    * do we also need to accommodate possible use of multibyte-character
253    * encodings?  (Hopefully, archives should rely exclusively on the
254    * POSIX Portable Character set, i.e. 7-bit ASCII, so maybe not).
255    */
256   char *parse, *parent, *mark = NULL, *stop = NULL;
257
258   /* We work with a copy of the supplied "pathname", so we can guarantee
259    * we have a modifiable string, and can accept a "const" input string.
260    */
261   if( (parse = parent = strdup( pathname )) != NULL )
262   {
263     /* Having obtained a valid copy of "pathname", we parse it...
264      */
265     while( *parse )
266     {
267       /* Set the "stop" mark at the first in the last sequence of
268        * one or more directory separators detected, (if any)...
269        */
270       stop = mark;
271       /*
272        * ...then step over any following characters which are
273        * neither the string terminator, nor further separators.
274        */
275       while( *parse && (*parse != '/') && (*parse != '\\') )
276         ++parse;
277
278       /* If we haven't yet found the string terminator, then we
279        * must have found a new sequence of one or more separators;
280        * mark it as a new candidate "stop" mark location.
281        */
282       if( *parse )
283         mark = parse;
284
285       /* Now, step over all contiguous separators in the current
286        * sequence, before restarting the outer loop, to parse the
287        * next directory or file name, (if any).  Note that we defer
288        * updating the "stop" mark until the start of this new cycle;
289        * this ensures that we correctly ignore any separators which
290        * trail at the end of "pathname", with no following "name"
291        * component.
292        */
293       while( (*parse == '/') || (*parse == '\\') )
294         ++parse;
295     }
296     if( stop != NULL )
297     {
298       /* We found a valid point, at which to split the current leaf
299        * of "pathname" from its parent branch hierarchy; split it and
300        * recurse through the "mkdir" function, to create the parent
301        * directory hierarchy, as required.
302        */
303       *stop = '\0';
304       mkdir_recursive( parent, mode );
305     }
306
307     /* We are now done with our temporary copy of "pathname"; reclaim
308      * the memory which was allocated to store it.
309      */
310     free( parent );
311   }
312 }
313
314 int mkdir_recursive( const char *pathname, int mode )
315 {
316   /* Public entry point for the recursive "mkdir" function.
317    *
318    * First, we attempt a simple "mkdir"; if this succeeds, the
319    * parent directory branch is already in place, and we have
320    * nothing more to do.
321    */
322   if( mkdir( pathname, mode ) == 0 )
323     return 0;
324
325   /* Otherwise...
326    */
327   switch( errno )
328   {
329     case ENOENT:
330       /*
331        * This indicates a gap in the parent branch hierarchy;
332        * call the preceding helper, to fill the gap...
333        */
334       create_parent_directory_hierarchy( pathname, mode );
335       /*
336        * ...before making a further attempt to add the leaf.
337        */
338       return mkdir( pathname, mode );
339
340     case EEXIST:
341       {
342         /* Here, the initial "mkdir" failed because a file
343          * system entity called "pathname" already exists; if
344          * this is already a directory, all is well; (there is
345          * no need to create it again)...
346          */
347         struct stat target;
348         if( (stat( pathname, &target ) == 0) && S_ISDIR( target.st_mode ) )
349           return 0;
350       }
351   }
352   /* ...otherwise we simply fall through and fail...
353    */
354   return -1;
355 }
356
357 int set_output_stream( const char *pathname, int mode )
358 {
359   /* Attach the extractor's output data stream to a specified file,
360    * creating the parent directory branch hierarchy as required, and
361    * return a file descriptor on the stream.
362    */
363   int fd;
364
365   /* First, simply attempt to create the destination file...
366    */
367   if( ((fd = creat( pathname, mode )) < 0) && (errno == ENOENT) )
368   {
369     /* ...but, on failure due to a gap in the directory structure
370      * call the preceding helper to create the necessary hierarchy...
371      */
372     create_parent_directory_hierarchy( pathname, 0755 );
373     /*
374      * ...before making a further attempt to create the file.
375      */
376     return creat( pathname, mode );
377   }
378
379   /* Here, we will have the invalid file descriptor from the initial
380    * failed attempt to create the file; we return it to indicate the
381    * ultimate failure to create this file.
382    */
383   return fd;
384 }
385
386 /* $RCSfile$: end of file */