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