Line data Source code
1 : // Copyright (c) 2013-2026 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief Function to create directories from a full path.
22 : *
23 : * This function is similar to the `mkdir -p <path>` command line. Further,
24 : * our implementation supports the filename within the path. You can also
25 : * define the name of the user and group you want the child most directory
26 : * to be in the end.
27 : */
28 :
29 : // self
30 : //
31 : #include <snapdev/chownnm.h>
32 : #include <snapdev/tokenize_string.h>
33 :
34 :
35 : // C++
36 : //
37 : #include <list>
38 :
39 :
40 : // C
41 : //
42 : #include <sys/stat.h>
43 : #include <sys/types.h>
44 :
45 :
46 :
47 :
48 :
49 : namespace snapdev
50 : {
51 :
52 :
53 : /** \brief Create directory as with `mkdir -p ...`.
54 : *
55 : * This function creates all the directories so one can create a file
56 : * under the deepest directory specified in \p path.
57 : *
58 : * If \p path includes the filename, then make sure to set
59 : * \p include_filename parameter to true. That way this function
60 : * ignores that name.
61 : *
62 : * You may also pass the mode and owner/group details for the new
63 : * or existing directories. The function ignores these parameters if
64 : * set to their default (0 for mode and an empty string for owner
65 : * and group.)
66 : *
67 : * The default mode of 0755 is used when creating a directory if you
68 : * used 0 as the mode. In most cases, acceptable modes are:
69 : *
70 : * \li 0700
71 : * \li 0770
72 : * \li 0775
73 : * \li 0750
74 : * \li 0755
75 : *
76 : * Other modes are likely to not be useful for a directory.
77 : *
78 : * The owner and group parameters can be set to specific user and group
79 : * names. These names must existing in /etc/passwd and /etc/group. When
80 : * both are empty strings, chown() is not called.
81 : *
82 : * The function accepts paths with double slashes as if there was
83 : * just one (i.e. "/etc//snapwebsites" is viewed as "/etc/snapwebsites".)
84 : * This is the standard Unix behavior.
85 : *
86 : * The function returns -1 if one or more of the directories cannot
87 : * be created or adjusted according to the parameters. It also logs
88 : * a message to your log file specifying which directory could not
89 : * be created.
90 : *
91 : * If the function returns -1, then errno is set to the error returned
92 : * by the last mkdir(), chmod(), or chown() that failed.
93 : *
94 : * \note
95 : * The two main errors when this function fails are: (1) the directory
96 : * cannot be created because you do not have enough permissions; and
97 : * (2) the named directory exists in the form of a file which is not
98 : * a directory.
99 : *
100 : * \bug
101 : * Many of the default directories that we need to have to run our
102 : * servers are to be created in directories that are owned by root.
103 : * This causes problems when attempting to run Snap! executables
104 : * as a developer.
105 : *
106 : * \param[in] path The path to create.
107 : * \param[in] include_filename If true, the \p path parameter also
108 : * includes a filename.
109 : * \param[in] mode The new directories mode if not zero.
110 : * \param[in] owner The new directories owner.
111 : * \param[in] group The new directories group.
112 : *
113 : * \return 0 if the directory exists at the time the function returns,
114 : * -1 if an error occurs (i.e. permissions denied)
115 : */
116 23 : inline int mkdir_p(
117 : std::string const & path
118 : , bool include_filename = false
119 : , int mode = 0
120 : , std::string const & owner = std::string()
121 : , std::string const & group = std::string())
122 : {
123 : // we skip empty parts since "//" is the same as "/" in a Unix path.
124 : //
125 23 : std::list<std::string> segments;
126 69 : tokenize_string(segments, path, "/", true);
127 23 : if(segments.empty())
128 : {
129 16 : return 0;
130 : }
131 :
132 7 : if(include_filename)
133 : {
134 5 : segments.pop_back();
135 : }
136 :
137 7 : std::string p;
138 7 : bool add_slash(path[0] == '/');
139 7 : std::size_t const max_segments(segments.size());
140 79 : for(std::size_t idx(0); idx < max_segments; ++idx)
141 : {
142 : // compute path
143 : //
144 74 : if(add_slash)
145 : {
146 74 : p += "/";
147 : }
148 : else
149 : {
150 0 : add_slash = true;
151 : }
152 :
153 74 : p += segments.front();
154 74 : segments.pop_front();
155 :
156 : // already exists?
157 : //
158 74 : struct stat s;
159 74 : if(stat(p.c_str(), &s) == 0)
160 : {
161 : // the file exists, it is a directory?
162 : //
163 66 : if(S_ISDIR(s.st_mode))
164 : {
165 : // make sure the last segment (directory we are really
166 : // expected to create) has the correct mode and ownership
167 : //
168 64 : if(idx + 1 == max_segments)
169 : {
170 2 : if(mode != 0)
171 : {
172 1 : if(chmod(p.c_str(), mode) != 0)
173 : {
174 0 : return -1;
175 : }
176 : }
177 2 : if(chownnm(p, owner, group) != 0)
178 : {
179 0 : return -1;
180 : }
181 : }
182 64 : continue;
183 : }
184 :
185 : // not a directory, that is an error
186 : //
187 2 : errno = EEXIST;
188 2 : return -1;
189 : }
190 :
191 : // attempt creating
192 : //
193 8 : if(mkdir(p.c_str(), mode == 0 ? 0755 : mode) != 0)
194 : {
195 0 : return -1;
196 : }
197 :
198 : // directories we create are also assigned owner/group
199 : //
200 8 : if(chownnm(p, owner, group) != 0)
201 : {
202 0 : return -1;
203 : }
204 : }
205 :
206 5 : return 0;
207 23 : }
208 :
209 :
210 :
211 : } // namespace snapdev
212 : // vim: ts=4 sw=4 et
|