xref: /6.6.0/platform/src/dirutils.cc (revision de77d527)
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
40static 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
71DIRUTILS_PUBLIC_API
72std::string cb::io::dirname(const std::string& dir) {
73    return split(dir, true);
74}
75
76DIRUTILS_PUBLIC_API
77std::string cb::io::basename(const std::string& name) {
78    return split(name, false);
79}
80
81#ifdef _MSC_VER
82DIRUTILS_PUBLIC_API
83std::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
111DIRUTILS_PUBLIC_API
112std::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
137DIRUTILS_PUBLIC_API
138std::vector<std::string> cb::io::findFilesWithPrefix(const std::string& name) {
139    return findFilesWithPrefix(dirname(name), basename(name));
140}
141
142#ifdef _MSC_VER
143DIRUTILS_PUBLIC_API
144std::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
172DIRUTILS_PUBLIC_API
173std::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
198DIRUTILS_PUBLIC_API
199void 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
268DIRUTILS_PUBLIC_API
269bool 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
285DIRUTILS_PUBLIC_API
286bool 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
302DIRUTILS_PUBLIC_API
303void 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
376std::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
386std::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
405uint64_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
470DIRUTILS_PUBLIC_API
471std::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