1 /* mkdir -- make directories
2    Copyright (C) 1990-2023 Free Software Foundation, Inc.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16 
17 /* David MacKenzie <djm@ai.mit.edu>  */
18 
19 #include <config.h>
20 #include <stdio.h>
21 #include <getopt.h>
22 #include <sys/types.h>
23 #include <selinux/label.h>
24 
25 #include "system.h"
26 #include "mkdir-p.h"
27 #include "modechange.h"
28 #include "prog-fprintf.h"
29 #include "quote.h"
30 #include "savewd.h"
31 #include "selinux.h"
32 #include "smack.h"
33 
34 /* The official name of this program (e.g., no 'g' prefix).  */
35 #define PROGRAM_NAME "mkdir"
36 
37 #define AUTHORS proper_name ("David MacKenzie")
38 
39 static struct option const longopts[] =
40 {
41   {GETOPT_SELINUX_CONTEXT_OPTION_DECL},
42   {"mode", required_argument, nullptr, 'm'},
43   {"parents", no_argument, nullptr, 'p'},
44   {"verbose", no_argument, nullptr, 'v'},
45   {GETOPT_HELP_OPTION_DECL},
46   {GETOPT_VERSION_OPTION_DECL},
47   {nullptr, 0, nullptr, 0}
48 };
49 
50 void
usage(int status)51 usage (int status)
52 {
53   if (status != EXIT_SUCCESS)
54     emit_try_help ();
55   else
56     {
57       printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name);
58       fputs (_("\
59 Create the DIRECTORY(ies), if they do not already exist.\n\
60 "), stdout);
61 
62       emit_mandatory_arg_note ();
63 
64       fputs (_("\
65   -m, --mode=MODE   set file mode (as in chmod), not a=rwx - umask\n\
66   -p, --parents     no error if existing, make parent directories as needed,\n\
67                     with their file modes unaffected by any -m option.\n\
68   -v, --verbose     print a message for each created directory\n\
69 "), stdout);
70       fputs (_("\
71   -Z                   set SELinux security context of each created directory\n\
72                          to the default type\n\
73       --context[=CTX]  like -Z, or if CTX is specified then set the SELinux\n\
74                          or SMACK security context to CTX\n\
75 "), stdout);
76       fputs (HELP_OPTION_DESCRIPTION, stdout);
77       fputs (VERSION_OPTION_DESCRIPTION, stdout);
78       emit_ancillary_info (PROGRAM_NAME);
79     }
80   exit (status);
81 }
82 
83 /* Options passed to subsidiary functions.  */
84 struct mkdir_options
85 {
86   /* Function to make an ancestor, or nullptr if ancestors should not be
87      made.  */
88   int (*make_ancestor_function) (char const *, char const *, void *);
89 
90   /* Umask value for when making an ancestor.  */
91   mode_t umask_ancestor;
92 
93   /* Umask value for when making the directory itself.  */
94   mode_t umask_self;
95 
96   /* Mode for directory itself.  */
97   mode_t mode;
98 
99   /* File mode bits affected by MODE.  */
100   mode_t mode_bits;
101 
102   /* Set the SELinux File Context.  */
103   struct selabel_handle *set_security_context;
104 
105   /* If not null, format to use when reporting newly made directories.  */
106   char const *created_directory_format;
107 };
108 
109 /* Report that directory DIR was made, if OPTIONS requests this.  */
110 static void
announce_mkdir(char const * dir,void * options)111 announce_mkdir (char const *dir, void *options)
112 {
113   struct mkdir_options const *o = options;
114   if (o->created_directory_format)
115     prog_fprintf (stdout, o->created_directory_format, quoteaf (dir));
116 }
117 
118 /* Make ancestor directory DIR, whose last component is COMPONENT,
119    with options OPTIONS.  Assume the working directory is COMPONENT's
120    parent.  Return 0 if successful and the resulting directory is
121    readable, 1 if successful but the resulting directory is not
122    readable, -1 (setting errno) otherwise.  */
123 static int
make_ancestor(char const * dir,char const * component,void * options)124 make_ancestor (char const *dir, char const *component, void *options)
125 {
126   struct mkdir_options const *o = options;
127 
128   if (o->set_security_context
129       && defaultcon (o->set_security_context, component, S_IFDIR) < 0
130       && ! ignorable_ctx_err (errno))
131     error (0, errno, _("failed to set default creation context for %s"),
132            quoteaf (dir));
133 
134   if (o->umask_ancestor != o->umask_self)
135     umask (o->umask_ancestor);
136   int r = mkdir (component, S_IRWXUGO);
137   if (o->umask_ancestor != o->umask_self)
138     {
139       int mkdir_errno = errno;
140       umask (o->umask_self);
141       errno = mkdir_errno;
142     }
143   if (r == 0)
144     {
145       r = (o->umask_ancestor & S_IRUSR) != 0;
146       announce_mkdir (dir, options);
147     }
148   return r;
149 }
150 
151 /* Process a command-line file name.  */
152 static int
process_dir(char * dir,struct savewd * wd,void * options)153 process_dir (char *dir, struct savewd *wd, void *options)
154 {
155   struct mkdir_options const *o = options;
156 
157   /* If possible set context before DIR created.  */
158   if (o->set_security_context)
159     {
160       if (! o->make_ancestor_function
161           && defaultcon (o->set_security_context, dir, S_IFDIR) < 0
162           && ! ignorable_ctx_err (errno))
163         error (0, errno, _("failed to set default creation context for %s"),
164                quoteaf (dir));
165     }
166 
167   int ret = (make_dir_parents (dir, wd, o->make_ancestor_function, options,
168                                o->mode, announce_mkdir,
169                                o->mode_bits, (uid_t) -1, (gid_t) -1, true)
170              ? EXIT_SUCCESS
171              : EXIT_FAILURE);
172 
173   /* FIXME: Due to the current structure of make_dir_parents()
174      we don't have the facility to call defaultcon() before the
175      final component of DIR is created.  So for now, create the
176      final component with the context from previous component
177      and here we set the context for the final component. */
178   if (ret == EXIT_SUCCESS && o->set_security_context
179       && o->make_ancestor_function)
180     {
181       if (! restorecon (o->set_security_context, last_component (dir), false)
182           && ! ignorable_ctx_err (errno))
183         error (0, errno, _("failed to restore context for %s"),
184                quoteaf (dir));
185     }
186 
187   return ret;
188 }
189 
190 int
main(int argc,char ** argv)191 main (int argc, char **argv)
192 {
193   char const *specified_mode = nullptr;
194   int optc;
195   char const *scontext = nullptr;
196   struct mkdir_options options;
197 
198   options.make_ancestor_function = nullptr;
199   options.mode = S_IRWXUGO;
200   options.mode_bits = 0;
201   options.created_directory_format = nullptr;
202   options.set_security_context = nullptr;
203 
204   initialize_main (&argc, &argv);
205   set_program_name (argv[0]);
206   setlocale (LC_ALL, "");
207   bindtextdomain (PACKAGE, LOCALEDIR);
208   textdomain (PACKAGE);
209 
210   atexit (close_stdout);
211 
212   while ((optc = getopt_long (argc, argv, "pm:vZ", longopts, nullptr)) != -1)
213     {
214       switch (optc)
215         {
216         case 'p':
217           options.make_ancestor_function = make_ancestor;
218           break;
219         case 'm':
220           specified_mode = optarg;
221           break;
222         case 'v': /* --verbose  */
223           options.created_directory_format = _("created directory %s");
224           break;
225         case 'Z':
226           if (is_smack_enabled ())
227             {
228               /* We don't yet support -Z to restore context with SMACK.  */
229               scontext = optarg;
230             }
231           else if (is_selinux_enabled () > 0)
232             {
233               if (optarg)
234                 scontext = optarg;
235               else
236                 {
237                   options.set_security_context = selabel_open (SELABEL_CTX_FILE,
238                                                                nullptr, 0);
239                   if (! options.set_security_context)
240                     error (0, errno, _("warning: ignoring --context"));
241                 }
242             }
243           else if (optarg)
244             {
245               error (0, 0,
246                      _("warning: ignoring --context; "
247                        "it requires an SELinux/SMACK-enabled kernel"));
248             }
249           break;
250         case_GETOPT_HELP_CHAR;
251         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
252         default:
253           usage (EXIT_FAILURE);
254         }
255     }
256 
257   if (optind == argc)
258     {
259       error (0, 0, _("missing operand"));
260       usage (EXIT_FAILURE);
261     }
262 
263   /* FIXME: This assumes mkdir() is done in the same process.
264      If that's not always the case we would need to call this
265      like we do when options.set_security_context.  */
266   if (scontext)
267     {
268       int ret = 0;
269       if (is_smack_enabled ())
270         ret = smack_set_label_for_self (scontext);
271       else
272         ret = setfscreatecon (scontext);
273 
274       if (ret < 0)
275         error (EXIT_FAILURE, errno,
276                _("failed to set default file creation context to %s"),
277                quote (scontext));
278     }
279 
280 
281   if (options.make_ancestor_function || specified_mode)
282     {
283       mode_t umask_value = umask (0);
284       options.umask_ancestor = umask_value & ~(S_IWUSR | S_IXUSR);
285 
286       if (specified_mode)
287         {
288           struct mode_change *change = mode_compile (specified_mode);
289           if (!change)
290             error (EXIT_FAILURE, 0, _("invalid mode %s"),
291                    quote (specified_mode));
292           options.mode = mode_adjust (S_IRWXUGO, true, umask_value, change,
293                                       &options.mode_bits);
294           options.umask_self = umask_value & ~options.mode;
295           free (change);
296         }
297       else
298         {
299           options.mode = S_IRWXUGO;
300           options.umask_self = umask_value;
301         }
302 
303       umask (options.umask_self);
304     }
305 
306   return savewd_process_files (argc - optind, argv + optind,
307                                process_dir, &options);
308 }
309