Line data Source code
1 : // Snap Websites Server -- like shell `mkdir -p ...`
2 : // Copyright (c) 2013-2019 Made to Order Software Corp. All Rights Reserved
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 2 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, write to the Free Software
16 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 :
18 :
19 : // self
20 : //
21 : #include "snapwebsites/mkdir_p.h"
22 :
23 :
24 : // snapwebsites lib
25 : //
26 : #include "snapwebsites/chownnm.h"
27 : #include "snapwebsites/log.h"
28 : #include "snapwebsites/snap_exception.h"
29 : #include "snapwebsites/snap_string_list.h"
30 :
31 :
32 : // snapdev lib
33 : //
34 : #include <snapdev/not_reached.h>
35 : #include <snapdev/not_used.h>
36 :
37 :
38 : // C++ lib
39 : //
40 : #include <sstream>
41 :
42 :
43 : // C lib
44 : //
45 : #include <sys/stat.h>
46 : #include <sys/types.h>
47 :
48 :
49 : // last include
50 : //
51 : #include <snapdev/poison.h>
52 :
53 :
54 :
55 : namespace snap
56 : {
57 :
58 :
59 : /** \brief Create directory as with `mkdir -p ...`.
60 : *
61 : * This function creates all the directories so one can create a file
62 : * under the deepest directory specified in \p path.
63 : *
64 : * If \p path includes the filename, then make sure to set
65 : * \p include_filename parameter to true. That way this function
66 : * ignores that name.
67 : *
68 : * You make also pass the mode and owner/group details for the new
69 : * or existing directory. The function ignores these parameters if
70 : * set to their default (0 for mode and an empty string for owner
71 : * and group.)
72 : *
73 : * The default mode of 0755 is used when creating a directory if you
74 : * used 0 as the mode. In most cases, acceptable modes are:
75 : *
76 : * \li 0700
77 : * \li 0770
78 : * \li 0775
79 : * \li 0750
80 : * \li 0755
81 : *
82 : * Other modes are likely to not be useful for a directory.
83 : *
84 : * The owner and group parameters can be set to specific user and group
85 : * names. These names must existing in /etc/passwd and /etc/group. When
86 : * both are empty strings, chown() is not called.
87 : *
88 : * The function accepts paths with double slashes as if there was
89 : * just one (i.e. "/etc//snapwebsites" is viewed as "/etc/snapwebsites".)
90 : * This is the standard Unix behavior.
91 : *
92 : * The function returns -1 if one or more of the directories cannot
93 : * be created or adjusted according to the parameters. It also logs
94 : * a message to your log file specifying which directory could not
95 : * be created.
96 : *
97 : * If the function returns -1, then errno is set to the error returned
98 : * by the last mkdir(), chmod(), or chown() that failed.
99 : *
100 : * \note
101 : * The two main errors when this function fails are: (1) the directory
102 : * cannot be created because you do not have enough permissions; and
103 : * (2) the named directory exists in the form of a file.
104 : *
105 : * \bug
106 : * Many of the default directories that we need to have to run our
107 : * servers are to be created in directories that are owned by root.
108 : * This causes problems when attempting to run Snap! executables
109 : * as a developer.
110 : *
111 : * \param[in] path The path to create.
112 : * \param[in] include_filename If true, the \p path parameter also
113 : * includes a filename.
114 : * \param[in] mode The new directories mode if not zero.
115 : * \param[in] owner The new directories owner.
116 : * \param[in] group The new directories group.
117 : *
118 : * \return 0 if the directory exists at the time the function returns,
119 : * -1 if an error occurs (i.e. permissions denied)
120 : */
121 1 : int mkdir_p(QString const & path
122 : , bool include_filename
123 : , int mode
124 : , QString const & owner
125 : , QString const & group)
126 : {
127 : // we skip empty parts since "//" is the same as "/" in a Unix path.
128 : //
129 2 : snap::snap_string_list segments(path.split('/', QString::SkipEmptyParts));
130 1 : if(segments.empty())
131 : {
132 0 : return 0;
133 : }
134 :
135 1 : if(include_filename)
136 : {
137 0 : segments.pop_back();
138 : }
139 :
140 2 : QString p;
141 1 : int const max_segments(segments.size());
142 5 : for(int idx(0); idx < max_segments; ++idx)
143 : {
144 : // compute path
145 4 : p += "/";
146 4 : p += segments[idx];
147 :
148 : // already exists?
149 : struct stat s;
150 4 : if(stat(p.toUtf8().data(), &s) == 0)
151 : {
152 : // the file exists, it is a directory?
153 : //
154 4 : if(S_ISDIR(s.st_mode))
155 : {
156 : // make sure the last segment (directory we are really
157 : // expected to create) has the correct mode and ownership
158 : //
159 4 : if(idx + 1 == max_segments)
160 : {
161 1 : if(mode != 0)
162 : {
163 1 : if(chmod(p.toUtf8().data(), mode) != 0)
164 : {
165 0 : int const e(errno);
166 0 : std::stringstream m;
167 0 : m << "0" << std::oct << mode;
168 0 : SNAP_LOG_DEBUG("could not change directory \"")
169 0 : (p)
170 0 : ("\" permissions to \"")
171 0 : (m.str())
172 0 : ("\". (errno: ")
173 0 : (e)
174 0 : (" -- ")
175 0 : (strerror(e));
176 : }
177 : }
178 1 : if(chownnm(p, owner, group) != 0)
179 : {
180 1 : int const e(errno);
181 2 : SNAP_LOG_DEBUG("could not change directory \"")
182 1 : (p)
183 1 : ("\" ownership to \"")
184 1 : (owner)
185 1 : (":")
186 1 : (group)
187 1 : ("\". (errno: ")
188 1 : (e)
189 1 : (" -- ")
190 1 : (strerror(e));
191 : }
192 : }
193 8 : continue;
194 : }
195 :
196 : // not a directory, that is an error
197 : //
198 0 : int const e(EEXIST);
199 0 : SNAP_LOG_ERROR("could not create directory \"")
200 0 : (p)
201 0 : ("\" since a file, which is not a directory, of the same name exists. (errno: ")
202 0 : (e)
203 0 : (" -- ")
204 0 : (strerror(e));
205 0 : errno = e;
206 0 : return -1;
207 : }
208 :
209 : // attempt creating
210 : //
211 0 : if(mkdir(p.toUtf8().data(), mode == 0 ? 0755 : mode) != 0)
212 : {
213 0 : int const e(errno);
214 0 : SNAP_LOG_ERROR("could not create directory \"")
215 0 : (p)
216 0 : ("\". (errno: ")
217 0 : (e)
218 0 : (" -- ")
219 0 : (strerror(e));
220 0 : errno = e;
221 0 : return -1;
222 : }
223 :
224 : // directories we create are also assigned owner/group
225 : //
226 0 : if(chownnm(p, owner, group) != 0)
227 : {
228 0 : int const e(errno);
229 0 : SNAP_LOG_DEBUG("could not change directory \"")
230 0 : (p)
231 0 : ("\" ownership to \"")
232 0 : (owner)
233 0 : (":")
234 0 : (group)
235 0 : ("\". (errno: ")
236 0 : (e)
237 0 : (" -- ")
238 0 : (strerror(e));
239 : }
240 : }
241 :
242 1 : return 0;
243 : }
244 :
245 :
246 1 : int mkdir_p(std::string const & path
247 : , bool include_filename
248 : , int mode
249 : , std::string const & owner
250 : , std::string const & group)
251 : {
252 2 : return mkdir_p(QString::fromUtf8(path.c_str())
253 : , include_filename
254 : , mode
255 2 : , QString::fromUtf8(owner.c_str())
256 3 : , QString::fromUtf8(group.c_str()));
257 : }
258 :
259 :
260 0 : int mkdir_p(char const * path
261 : , bool include_filename
262 : , int mode
263 : , char const * owner
264 : , char const * group)
265 : {
266 0 : return mkdir_p(QString::fromUtf8(path)
267 : , include_filename
268 : , mode
269 0 : , QString::fromUtf8(owner == nullptr ? "" : owner)
270 0 : , QString::fromUtf8(group == nullptr ? "" : group));
271 : }
272 :
273 :
274 6 : } // snap namespace
275 : // vim: ts=4 sw=4 et
|