hush: correct exitcode for unterminated ')' - exitcode2.tests testcase
[oweals/busybox.git] / libbb / make_directory.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * parse_mode implementation for busybox
4  *
5  * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9
10 /* Mar 5, 2003    Manuel Novoa III
11  *
12  * This is the main work function for the 'mkdir' applet.  As such, it
13  * strives to be SUSv3 compliant in it's behaviour when recursively
14  * making missing parent dirs, and in it's mode setting of the final
15  * directory 'path'.
16  *
17  * To recursively build all missing intermediate directories, make
18  * sure that (flags & FILEUTILS_RECUR) is non-zero.  Newly created
19  * intermediate directories will have at least u+wx perms.
20  *
21  * To set specific permissions on 'path', pass the appropriate 'mode'
22  * val.  Otherwise, pass -1 to get default permissions.
23  */
24
25 #include "libbb.h"
26
27 /* This function is used from NOFORK applets. It must not allocate anything */
28
29 int FAST_FUNC bb_make_directory(char *path, long mode, int flags)
30 {
31         mode_t cur_mask;
32         mode_t org_mask;
33         const char *fail_msg;
34         char *s;
35         char c;
36         struct stat st;
37
38         /* "path" can be a result of dirname().
39          * dirname("no_slashes") returns ".", possibly read-only.
40          * musl dirname() can return read-only "/" too.
41          * We need writable string. And for "/", "." (and ".."?)
42          * nothing needs to be created anyway.
43          */
44         if (LONE_CHAR(path, '/'))
45                 return 0;
46         if (path[0] == '.') {
47                 if (path[1] == '\0')
48                         return 0; /* "." */
49 //              if (path[1] == '.' && path[2] == '\0')
50 //                      return 0; /* ".." */
51         }
52
53         org_mask = cur_mask = (mode_t)-1L;
54         s = path;
55         while (1) {
56                 c = '\0';
57
58                 if (flags & FILEUTILS_RECUR) {  /* Get the parent */
59                         /* Bypass leading non-'/'s and then subsequent '/'s */
60                         while (*s) {
61                                 if (*s == '/') {
62                                         do {
63                                                 ++s;
64                                         } while (*s == '/');
65                                         c = *s; /* Save the current char */
66                                         *s = '\0'; /* and replace it with nul */
67                                         break;
68                                 }
69                                 ++s;
70                         }
71                 }
72
73                 if (c != '\0') {
74                         /* Intermediate dirs: must have wx for user */
75                         if (cur_mask == (mode_t)-1L) { /* wasn't done yet? */
76                                 mode_t new_mask;
77                                 org_mask = umask(0);
78                                 cur_mask = 0;
79                                 /* Clear u=wx in umask - this ensures
80                                  * they won't be cleared on mkdir */
81                                 new_mask = (org_mask & ~(mode_t)0300);
82                                 //bb_error_msg("org_mask:%o cur_mask:%o", org_mask, new_mask);
83                                 if (new_mask != cur_mask) {
84                                         cur_mask = new_mask;
85                                         umask(new_mask);
86                                 }
87                         }
88                 } else {
89                         /* Last component: uses original umask */
90                         //bb_error_msg("1 org_mask:%o", org_mask);
91                         if (org_mask != cur_mask) {
92                                 cur_mask = org_mask;
93                                 umask(org_mask);
94                         }
95                 }
96
97                 if (mkdir(path, 0777) < 0) {
98                         /* If we failed for any other reason than the directory
99                          * already exists, output a diagnostic and return -1 */
100                         if ((errno != EEXIST && errno != EISDIR)
101                          || !(flags & FILEUTILS_RECUR)
102                          || ((stat(path, &st) < 0) || !S_ISDIR(st.st_mode))
103                         ) {
104                                 fail_msg = "create";
105                                 break;
106                         }
107                         /* Since the directory exists, don't attempt to change
108                          * permissions if it was the full target.  Note that
109                          * this is not an error condition. */
110                         if (!c) {
111                                 goto ret0;
112                         }
113                 } else {
114                         if (flags & FILEUTILS_VERBOSE) {
115                                 printf("created directory: '%s'\n", path);
116                         }
117                 }
118
119                 if (!c) {
120                         /* Done.  If necessary, update perms on the newly
121                          * created directory.  Failure to update here _is_
122                          * an error. */
123                         if ((mode != -1) && (chmod(path, mode) < 0)) {
124                                 fail_msg = "set permissions of";
125                                 if (flags & FILEUTILS_IGNORE_CHMOD_ERR) {
126                                         flags = 0;
127                                         goto print_err;
128                                 }
129                                 break;
130                         }
131                         goto ret0;
132                 }
133
134                 /* Remove any inserted nul from the path (recursive mode) */
135                 *s = c;
136         } /* while (1) */
137
138         flags = -1;
139  print_err:
140         bb_perror_msg("can't %s directory '%s'", fail_msg, path);
141         goto ret;
142  ret0:
143         flags = 0;
144  ret:
145         //bb_error_msg("2 org_mask:%o", org_mask);
146         if (org_mask != cur_mask)
147                 umask(org_mask);
148         return flags;
149 }