1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2013 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include "config.h"
18 #include <platform/dirutils.h>
19 
20 #ifdef _MSC_VER
21 #include <direct.h>
22 #include <fileapi.h>
23 #include <shlobj.h>
24 #define rmdir _rmdir
25 #else
26 
27 #include <dirent.h>
28 #include <unistd.h>
29 
30 #endif
31 
32 #include <platform/memorymap.h>
33 #include <platform/strerror.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <limits>
38 #include <system_error>
39 
split(const std::string& input, bool directory)40 static std::string split(const std::string& input, bool directory) {
41     std::string::size_type path = input.find_last_of("\\/");
42     std::string file;
43     std::string dir;
44 
45     if (path == std::string::npos) {
46         dir = ".";
47         file = input;
48     } else {
49         dir = input.substr(0, path);
50         if (dir.length() == 0) {
51             dir = input.substr(0, 1);
52         }
53 
54         while (dir.length() > 1 &&
55                dir.find_last_of("\\/") == (dir.length() - 1)) {
56             if (dir.length() > 1) {
57                 dir.resize(dir.length() - 1);
58             }
59         }
60 
61         file = input.substr(path + 1);
62     }
63 
64     if (directory) {
65         return dir;
66     } else {
67         return file;
68     }
69 }
70 
71 DIRUTILS_PUBLIC_API
dirname(const std::string& dir)72 std::string cb::io::dirname(const std::string& dir) {
73     return split(dir, true);
74 }
75 
76 DIRUTILS_PUBLIC_API
basename(const std::string& name)77 std::string cb::io::basename(const std::string& name) {
78     return split(name, false);
79 }
80 
81 #ifdef _MSC_VER
82 DIRUTILS_PUBLIC_API
findFilesWithPrefix(const std::string &dir, const std::string &name)83 std::vector<std::string> cb::io::findFilesWithPrefix(const std::string &dir,
84                                                      const std::string &name)
85 {
86     std::vector<std::string> files;
87     std::string match = dir + "\\" + name + "*";
88     WIN32_FIND_DATA FindFileData;
89 
90     HANDLE hFind = FindFirstFileEx(match.c_str(), FindExInfoStandard,
91                                    &FindFileData, FindExSearchNameMatch,
92                                    NULL, 0);
93 
94     if (hFind != INVALID_HANDLE_VALUE) {
95         do {
96             std::string fnm(FindFileData.cFileName);
97             if (fnm != "." && fnm != "..") {
98                 std::string entry = dir;
99                 entry.append("\\");
100                 entry.append(FindFileData.cFileName);
101                 files.push_back(entry);
102             }
103         } while (FindNextFile(hFind, &FindFileData));
104 
105         FindClose(hFind);
106     }
107     return files;
108 }
109 #else
110 
111 DIRUTILS_PUBLIC_API
findFilesWithPrefix(const std::string& dir, const std::string& name)112 std::vector<std::string> cb::io::findFilesWithPrefix(const std::string& dir,
113                                                      const std::string& name) {
114     std::vector<std::string> files;
115     DIR* dp = opendir(dir.c_str());
116     if (dp != NULL) {
117         struct dirent* de;
118         while ((de = readdir(dp)) != NULL) {
119             std::string fnm(de->d_name);
120             if (fnm == "." || fnm == "..") {
121                 continue;
122             }
123             if (strncmp(de->d_name, name.c_str(), name.length()) == 0) {
124                 std::string entry = dir;
125                 entry.append("/");
126                 entry.append(de->d_name);
127                 files.push_back(entry);
128             }
129         }
130 
131         closedir(dp);
132     }
133     return files;
134 }
135 #endif
136 
137 DIRUTILS_PUBLIC_API
findFilesWithPrefix(const std::string& name)138 std::vector<std::string> cb::io::findFilesWithPrefix(const std::string& name) {
139     return findFilesWithPrefix(dirname(name), basename(name));
140 }
141 
142 #ifdef _MSC_VER
143 DIRUTILS_PUBLIC_API
findFilesContaining(const std::string &dir, const std::string &name)144 std::vector<std::string> cb::io::findFilesContaining(const std::string &dir,
145                                                             const std::string &name)
146 {
147     std::vector<std::string> files;
148     std::string match = dir + "\\*" + name + "*";
149     WIN32_FIND_DATA FindFileData;
150 
151     HANDLE hFind = FindFirstFileEx(match.c_str(), FindExInfoStandard,
152                                    &FindFileData, FindExSearchNameMatch,
153                                    NULL, 0);
154 
155     if (hFind != INVALID_HANDLE_VALUE) {
156         do {
157             std::string fnm(FindFileData.cFileName);
158             if (fnm != "." && fnm != "..") {
159                 std::string entry = dir;
160                 entry.append("\\");
161                 entry.append(FindFileData.cFileName);
162                 files.push_back(entry);
163             }
164         } while (FindNextFile(hFind, &FindFileData));
165 
166         FindClose(hFind);
167     }
168     return files;
169 }
170 #else
171 
172 DIRUTILS_PUBLIC_API
findFilesContaining(const std::string& dir, const std::string& name)173 std::vector<std::string> cb::io::findFilesContaining(const std::string& dir,
174                                                             const std::string& name) {
175     std::vector<std::string> files;
176     DIR* dp = opendir(dir.c_str());
177     if (dp != NULL) {
178         struct dirent* de;
179         while ((de = readdir(dp)) != NULL) {
180             if (name.empty() || strstr(de->d_name, name.c_str()) != NULL) {
181                 std::string fnm(de->d_name);
182                 if (fnm != "." && fnm != "..") {
183                     std::string entry = dir;
184                     entry.append("/");
185                     entry.append(de->d_name);
186                     files.push_back(entry);
187                 }
188             }
189         }
190 
191         closedir(dp);
192     }
193 
194     return files;
195 }
196 #endif
197 
198 DIRUTILS_PUBLIC_API
rmrf(const std::string& path)199 void cb::io::rmrf(const std::string& path) {
200     struct stat st;
201     if (stat(path.c_str(), &st) == -1) {
202         throw std::system_error(errno, std::system_category(),
203                                 "cb::io::rmrf: stat of " +
204                                 path + " failed");
205     }
206 
207     if ((st.st_mode & S_IFDIR) != S_IFDIR) {
208         if (remove(path.c_str()) != 0) {
209             throw std::system_error(errno, std::system_category(),
210                                     "cb::io::rmrf: remove of " +
211                                     path + " failed");
212         }
213         return;
214     }
215 
216     if (rmdir(path.c_str()) == 0) {
217         return;
218     }
219 
220     // Ok, this is a directory. Go ahead and delete it recursively
221     std::vector<std::string> directories;
222     std::vector<std::string> emptyDirectories;
223     directories.push_back(path);
224 
225     // Iterate all the files/directories found in path, when we encounter
226     // a sub-directory, that is added to the directories vector so we move
227     // deeper into the path.
228     do {
229         std::vector<std::string> vec =
230                 findFilesContaining(directories.back(), "");
231         emptyDirectories.push_back(directories.back());
232         directories.pop_back();
233         std::vector<std::string>::iterator ii;
234 
235         for (ii = vec.begin(); ii != vec.end(); ++ii) {
236             if (stat(ii->c_str(), &st) == -1) {
237                 throw std::system_error(errno, std::system_category(),
238                                         "cb::io::rmrf: stat of file/directory " +
239                                         *ii + " under " + path + " failed");
240             }
241 
242             if ((st.st_mode & S_IFDIR) == S_IFDIR) {
243                 if (rmdir(ii->c_str()) != 0) {
244                     directories.push_back(*ii);
245                 }
246             } else if (remove(ii->c_str()) != 0) {
247                 throw std::system_error(errno,
248                                         std::system_category(),
249                                         "cb::io::rmrf: remove of file " + *ii +
250                                                 " under " + path + " failed");
251             }
252         }
253     } while (!directories.empty());
254 
255     // Finally iterate our list of now empty directories, we reverse iterate so
256     // that we remove the deepest first
257     for (auto itr = emptyDirectories.rbegin(); itr != emptyDirectories.rend();
258          ++itr) {
259         if (rmdir(itr->c_str()) != 0) {
260             throw std::system_error(
261                     errno,
262                     std::system_category(),
263                     "cb::io::rmrf: remove of directory " + *itr + " failed");
264         }
265     }
266 }
267 
268 DIRUTILS_PUBLIC_API
isDirectory(const std::string& directory)269 bool cb::io::isDirectory(const std::string& directory) {
270 #ifdef WIN32
271     DWORD dwAttrib = GetFileAttributes(directory.c_str());
272     if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
273         return false;
274     }
275     return (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
276 #else
277     struct stat st;
278     if (stat(directory.c_str(), &st) == -1) {
279         return false;
280     }
281     return (S_ISDIR(st.st_mode));
282 #endif
283 }
284 
285 DIRUTILS_PUBLIC_API
isFile(const std::string& file)286 bool cb::io::isFile(const std::string& file) {
287 #ifdef WIN32
288     DWORD dwAttrib = GetFileAttributes(file.c_str());
289     if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
290         return false;
291     }
292     return (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0;
293 #else
294     struct stat st;
295     if (stat(file.c_str(), &st) == -1) {
296         return false;
297     }
298     return (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode));
299 #endif
300 }
301 
302 DIRUTILS_PUBLIC_API
mkdirp(const std::string& directory)303 void cb::io::mkdirp(const std::string& directory) {
304     // Bail out immediately if the directory already exists.
305     // note that both mkdir and CreateDirectory on windows returns
306     // EEXISTS if the directory already exists, BUT it could also
307     // return "permission denied" depending on the order the checks
308     // is run within "libc"
309     if (isDirectory(directory)) {
310         return;
311     }
312 
313 #ifdef WIN32
314     // ensure directory path is absolute
315     // Initial call without buffer to find size of buffer needed
316     auto ret = GetFullPathNameA(directory.c_str(), 0, nullptr, nullptr);
317     if (!ret) {
318         throw std::system_error(GetLastError(),
319                                 std::system_category(),
320                                 "cb::io::mkdirp(\"" + directory +
321                                         "\") failed - could not get buffer "
322                                         "size for absolute path.");
323     }
324 
325     // make string of right size
326     std::string absPath(ret, '\0');
327     // populate absPath with the path
328     ret = GetFullPathNameA(directory.c_str(), /* rel or abs path */
329                            (DWORD)absPath.size(), /* dest buffer size */
330                            &absPath[0], /* dest buffer */
331                            nullptr /* don't need ptr to filePart */
332     );
333 
334     if (!ret) {
335         throw std::system_error(
336                 GetLastError(),
337                 std::system_category(),
338                 "cb::io::mkdirp(\"" + directory +
339                         "\") failed - could not get absolute path.");
340     }
341 
342     auto err = SHCreateDirectoryEx(nullptr, /* no window handle */
343                                    absPath.data(),
344                                    nullptr /* no security attrs */
345     );
346     if (err != ERROR_SUCCESS) {
347         throw std::system_error(
348                 err,
349                 std::system_category(),
350                 "cb::io::mkdirp(\"" + directory +
351                         "\") failed, could not create directory.");
352     }
353 #else
354     do {
355         if (mkdir(directory.c_str(), S_IREAD | S_IWRITE | S_IEXEC) == 0) {
356             return;
357         }
358 
359         switch (errno) {
360         case EEXIST:
361             return;
362         case ENOENT:
363             break;
364         default:
365             throw std::system_error(errno, std::system_category(),
366                                     "cb::io::mkdirp(\"" + directory +
367                                     "\") failed");
368         }
369 
370         // Try to create the parent directory..
371         mkdirp(dirname(directory));
372     } while (true);
373 #endif
374 }
375 
mktemp(const std::string& prefix)376 std::string cb::io::mktemp(const std::string& prefix) {
377     static const std::string patternmask{"XXXXXX"};
378     std::string pattern = prefix;
379     if (pattern.find(patternmask) == pattern.npos) {
380         pattern.append(patternmask);
381     }
382 
383     return std::string {cb_mktemp(const_cast<char*>(pattern.data()))};
384 }
385 
getcwd(void)386 std::string cb::io::getcwd(void) {
387     std::string result(4096, 0);
388 #ifdef WIN32
389     if (GetCurrentDirectory(result.size(), &result[0]) == 0) {
390         throw std::system_error(GetLastError(), std::system_category(),
391                                 "Failed to determine current working directory");
392     }
393 #else
394     if (::getcwd(&result[0], result.size()) == nullptr) {
395         throw std::system_error(errno, std::system_category(),
396                                 "Failed to determine current working directory");
397     }
398 #endif
399 
400     // Trim off any trailing \0 characters.
401     result.resize(strlen(result.c_str()));
402     return result;
403 }
404 
maximizeFileDescriptors(uint64_t limit)405 uint64_t cb::io::maximizeFileDescriptors(uint64_t limit) {
406 #ifdef WIN32
407     return limit;
408 #else
409     struct rlimit rlim;
410 
411     if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
412         throw std::system_error(errno, std::system_category(),
413                                 "getrlimit(RLIMIT_NOFILE, &rlim) failed");
414     } else if (limit <= rlim.rlim_cur) {
415         return uint64_t(rlim.rlim_cur);
416     } else {
417         struct rlimit org = rlim;
418 
419         rlim.rlim_cur = limit;
420 
421         // we don't want to limit the current max if it is higher ;)
422         if (rlim.rlim_max < limit) {
423             rlim.rlim_max = limit;
424         }
425 
426         if (setrlimit(RLIMIT_NOFILE, &rlim) == 0) {
427             return limit;
428         }
429 
430         // Ok, we failed to get what we wanted.. Try a binary search
431         // to go as high as we can...
432         auto min = org.rlim_cur;
433         auto max = limit;
434         rlim_t last_good = 0;
435 
436         while (min <= max) {
437             rlim_t avg;
438 
439             // make sure we don't overflow
440             uint64_t high = std::numeric_limits<uint64_t>::max() - min;
441             if (high < max) {
442                 avg = max / 2;
443             } else {
444                 avg = (min + max) / 2;
445             }
446 
447             rlim.rlim_cur = rlim.rlim_max = avg;
448             if (setrlimit(RLIMIT_NOFILE, &rlim) == 0) {
449                 last_good = avg;
450                 min = avg + 1;
451             } else {
452                 max = avg - 1;
453             }
454         }
455 
456         if (last_good == 0) {
457             // all setrlimit's failed... lets go fetch it again
458             if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
459                 throw std::system_error(errno, std::system_category(),
460                                         "getrlimit(RLIMIT_NOFILE, &rlim) failed");
461             }
462             return uint64_t(rlim.rlim_cur);
463         }
464 
465         return uint64_t(last_good);
466     }
467 #endif
468 }
469 
470 DIRUTILS_PUBLIC_API
loadFile(const std::string& name)471 std::string cb::io::loadFile(const std::string& name) {
472     cb::MemoryMappedFile map(name.c_str(), cb::MemoryMappedFile::Mode::RDONLY);
473     map.open();
474     return std::string{reinterpret_cast<char*>(map.getRoot()), map.getSize()};
475 }
476