OSDN Git Service

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