Import from coreutils.
[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 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include "mkancesdirs.h"
26
27 #include <errno.h>
28 #include <sys/stat.h>
29
30 #include "dirname.h"
31 #include "stat-macros.h"
32
33 /* Return 0 if FILE is a directory, otherwise -1 (setting errno).  */
34
35 static int
36 test_dir (char const *file)
37 {
38   struct stat st;
39   if (stat (file, &st) == 0)
40     {
41       if (S_ISDIR (st.st_mode))
42         return 0;
43       errno = ENOTDIR;
44     }
45   return -1;
46 }
47
48 /* Ensure that the ancestor directories of FILE exist, using an
49    algorithm that should work even if two processes execute this
50    function in parallel.  Temporarily modify FILE by storing '\0'
51    bytes into it, to access the ancestor directories.
52
53    Create any ancestor directories that don't already exist, by
54    invoking MAKE_DIR (ANCESTOR, MAKE_DIR_ARG).  This function should
55    return zero if successful, -1 (setting errno) otherwise.
56
57    If successful, return 0 with FILE set back to its original value;
58    otherwise, return -1 (setting errno), storing a '\0' into *FILE so
59    that it names the ancestor directory that had problems.  */
60
61 int
62 mkancesdirs (char *file,
63              int (*make_dir) (char const *, void *),
64              void *make_dir_arg)
65 {
66   /* This algorithm is O(N**2) but in typical practice the fancier
67      O(N) algorithms are slower.  */
68
69   /* Address of the previous directory separator that follows an
70      ordinary byte in a file name in the left-to-right scan, or NULL
71      if no such separator precedes the current location P.  */
72   char *sep = NULL;
73
74   char const *prefix_end = file + FILE_SYSTEM_PREFIX_LEN (file);
75   char *p;
76   char c;
77
78   /* Search backward through FILE using mkdir to create the
79      furthest-away ancestor that is needed.  This loop isn't needed
80      for correctness, but typically ancestors already exist so this
81      loop speeds things up a bit.
82
83      This loop runs a bit faster if errno initially contains an error
84      number corresponding to a failed access to FILE.  However, things
85      work correctly regardless of errno's initial value.  */
86
87   for (p = last_component (file); prefix_end < p; p--)
88     if (ISSLASH (*p) && ! ISSLASH (p[-1]))
89       {
90         *p = '\0';
91
92         if (errno == ENOENT && make_dir (file, make_dir_arg) == 0)
93           {
94             *p = '/';
95             break;
96           }
97
98         if (errno != ENOENT)
99           {
100             if (test_dir (file) == 0)
101               {
102                 *p = '/';
103                 break;
104               }
105             if (errno != ENOENT)
106               return -1;
107           }
108
109         *p = '/';
110       }
111
112   /* Scan forward through FILE, creating directories along the way.
113      Try mkdir before stat, so that the procedure works even when two
114      or more processes are executing it in parallel.  */
115
116   while ((c = *p++))
117     if (ISSLASH (*p))
118       {
119         if (! ISSLASH (c))
120           sep = p;
121       }
122     else if (ISSLASH (c) && *p && sep)
123       {
124         *sep = '\0';
125         if (make_dir (file, make_dir_arg) != 0 && test_dir (file) != 0)
126           return -1;
127         *sep = '/';
128       }
129
130
131   return 0;
132 }