* _fpending.c: Include <config.h> unconditionally, since we no
[gnulib.git] / lib / mkancesdirs.c
1 /* Make a file's ancestor directories.
2
3    Copyright (C) 2006 Free Software Foundation, Inc.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19 /* Written by Paul Eggert.  */
20
21 #include <config.h>
22
23 #include "mkancesdirs.h"
24
25 #include <errno.h>
26 #include <sys/stat.h>
27
28 #include "dirname.h"
29 #include "stat-macros.h"
30
31 /* Return 0 if FILE is a directory, otherwise -1 (setting errno).  */
32
33 static int
34 test_dir (char const *file)
35 {
36   struct stat st;
37   if (stat (file, &st) == 0)
38     {
39       if (S_ISDIR (st.st_mode))
40         return 0;
41       errno = ENOTDIR;
42     }
43   return -1;
44 }
45
46 /* Ensure that the ancestor directories of FILE exist, using an
47    algorithm that should work even if two processes execute this
48    function in parallel.  Temporarily modify FILE by storing '\0'
49    bytes into it, to access the ancestor directories.
50
51    Create any ancestor directories that don't already exist, by
52    invoking MAKE_DIR (ANCESTOR, MAKE_DIR_ARG).  This function should
53    return zero if successful, -1 (setting errno) otherwise.
54
55    If successful, return 0 with FILE set back to its original value;
56    otherwise, return -1 (setting errno), storing a '\0' into *FILE so
57    that it names the ancestor directory that had problems.  */
58
59 int
60 mkancesdirs (char *file,
61              int (*make_dir) (char const *, void *),
62              void *make_dir_arg)
63 {
64   /* This algorithm is O(N**2) but in typical practice the fancier
65      O(N) algorithms are slower.  */
66
67   /* Address of the previous directory separator that follows an
68      ordinary byte in a file name in the left-to-right scan, or NULL
69      if no such separator precedes the current location P.  */
70   char *sep = NULL;
71
72   char const *prefix_end = file + FILE_SYSTEM_PREFIX_LEN (file);
73   char *p;
74   char c;
75
76   /* Search backward through FILE using mkdir to create the
77      furthest-away ancestor that is needed.  This loop isn't needed
78      for correctness, but typically ancestors already exist so this
79      loop speeds things up a bit.
80
81      This loop runs a bit faster if errno initially contains an error
82      number corresponding to a failed access to FILE.  However, things
83      work correctly regardless of errno's initial value.  */
84
85   for (p = last_component (file); prefix_end < p; p--)
86     if (ISSLASH (*p) && ! ISSLASH (p[-1]))
87       {
88         *p = '\0';
89
90         if (errno == ENOENT && make_dir (file, make_dir_arg) == 0)
91           {
92             *p = '/';
93             break;
94           }
95
96         if (errno != ENOENT)
97           {
98             if (test_dir (file) == 0)
99               {
100                 *p = '/';
101                 break;
102               }
103             if (errno != ENOENT)
104               return -1;
105           }
106
107         *p = '/';
108       }
109
110   /* Scan forward through FILE, creating directories along the way.
111      Try mkdir before stat, so that the procedure works even when two
112      or more processes are executing it in parallel.  */
113
114   while ((c = *p++))
115     if (ISSLASH (*p))
116       {
117         if (! ISSLASH (c))
118           sep = p;
119       }
120     else if (ISSLASH (c) && *p && sep)
121       {
122         *sep = '\0';
123         if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0)
124           return -1;
125         *sep = '/';
126       }
127
128
129   return 0;
130 }