Line data Source code
1 : // Copyright (c) 2013-2023 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 23 : 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 : } // snap namespacedev 212 : // vim: ts=4 sw=4 et