OSDN Git Service

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