tar: fix bug 673 (misdetection of repeated dir as hardlink). +92 bytes
[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 tarball for details.
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         /* Happens on bb_make_directory(dirname("no_slashes"),...) */
39         if (LONE_CHAR(path, '.'))
40                 return 0;
41
42         org_mask = cur_mask = (mode_t)-1L;
43         s = path;
44         while (1) {
45                 c = '\0';
46
47                 if (flags & FILEUTILS_RECUR) {  /* Get the parent */
48                         /* Bypass leading non-'/'s and then subsequent '/'s */
49                         while (*s) {
50                                 if (*s == '/') {
51                                         do {
52                                                 ++s;
53                                         } while (*s == '/');
54                                         c = *s; /* Save the current char */
55                                         *s = '\0'; /* and replace it with nul */
56                                         break;
57                                 }
58                                 ++s;
59                         }
60                 }
61
62                 if (c != '\0') {
63                         /* Intermediate dirs: must have wx for user */
64                         if (cur_mask == (mode_t)-1L) { /* wasn't done yet? */
65                                 mode_t new_mask;
66                                 org_mask = umask(0);
67                                 cur_mask = 0;
68                                 /* Clear u=wx in umask - this ensures
69                                  * they won't be cleared on mkdir */
70                                 new_mask = (org_mask & ~(mode_t)0300);
71                                 //bb_error_msg("org_mask:%o cur_mask:%o", org_mask, new_mask);
72                                 if (new_mask != cur_mask) {
73                                         cur_mask = new_mask;
74                                         umask(new_mask);
75                                 }
76                         }
77                 } else {
78                         /* Last component: uses original umask */
79                         //bb_error_msg("1 org_mask:%o", org_mask);
80                         if (org_mask != cur_mask) {
81                                 cur_mask = org_mask;
82                                 umask(org_mask);
83                         }
84                 }
85
86                 if (mkdir(path, 0777) < 0) {
87                         /* If we failed for any other reason than the directory
88                          * already exists, output a diagnostic and return -1 */
89                         if (errno != EEXIST
90                          || !(flags & FILEUTILS_RECUR)
91                          || ((stat(path, &st) < 0) || !S_ISDIR(st.st_mode))
92                         ) {
93                                 fail_msg = "create";
94                                 break;
95                         }
96                         /* Since the directory exists, don't attempt to change
97                          * permissions if it was the full target.  Note that
98                          * this is not an error condition. */
99                         if (!c) {
100                                 goto ret0;
101                         }
102                 }
103
104                 if (!c) {
105                         /* Done.  If necessary, update perms on the newly
106                          * created directory.  Failure to update here _is_
107                          * an error. */
108                         if ((mode != -1) && (chmod(path, mode) < 0)) {
109                                 fail_msg = "set permissions of";
110                                 break;
111                         }
112                         goto ret0;
113                 }
114
115                 /* Remove any inserted nul from the path (recursive mode) */
116                 *s = c;
117         } /* while (1) */
118
119         bb_perror_msg("can't %s directory '%s'", fail_msg, path);
120         flags = -1;
121         goto ret;
122  ret0:
123         flags = 0;
124  ret:
125         //bb_error_msg("2 org_mask:%o", org_mask);
126         if (org_mask != cur_mask)
127                 umask(org_mask);
128         return flags;
129 }