/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "otautil/dirutil.h" #include #include #include #include #include #include #include #include #include enum class DirStatus { DMISSING, DDIR, DILLEGAL }; static DirStatus dir_status(const std::string& path) { struct stat sb; if (stat(path.c_str(), &sb) == 0) { // Something's there; make sure it's a directory. if (S_ISDIR(sb.st_mode)) { return DirStatus::DDIR; } errno = ENOTDIR; return DirStatus::DILLEGAL; } else if (errno != ENOENT) { // Something went wrong, or something in the path is bad. Can't do anything in this situation. return DirStatus::DILLEGAL; } return DirStatus::DMISSING; } int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename, const selabel_handle* sehnd) { return mkdir_recursively(input_path, mode, strip_filename, sehnd, NULL); } int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename, const selabel_handle* sehnd, const struct utimbuf* timestamp) { // Check for an empty string before we bother making any syscalls. if (input_path.empty()) { errno = ENOENT; return -1; } // Allocate a path that we can modify; stick a slash on the end to make things easier. std::string path = input_path; if (strip_filename) { // Strip everything after the last slash. size_t pos = path.rfind('/'); if (pos == std::string::npos) { errno = ENOENT; return -1; } path.resize(pos + 1); } else { // Make sure that the path ends in a slash. path.push_back('/'); } // See if it already exists. DirStatus ds = dir_status(path); if (ds == DirStatus::DDIR) { return 0; } else if (ds == DirStatus::DILLEGAL) { return -1; } // Walk up the path from the root and make each level. size_t prev_end = 0; while (prev_end < path.size()) { size_t next_end = path.find('/', prev_end + 1); if (next_end == std::string::npos) { break; } std::string dir_path = path.substr(0, next_end); // Check this part of the path and make a new directory if necessary. switch (dir_status(dir_path)) { case DirStatus::DILLEGAL: // Could happen if some other process/thread is messing with the filesystem. return -1; case DirStatus::DMISSING: { char* secontext = nullptr; if (sehnd) { selabel_lookup(const_cast(sehnd), &secontext, dir_path.c_str(), mode); setfscreatecon(secontext); } int err = mkdir(dir_path.c_str(), mode); if (secontext) { freecon(secontext); setfscreatecon(nullptr); } if (err != 0) { return -1; } if (timestamp != NULL && utime(dir_path.c_str(), timestamp)) { return -1; } break; } default: // Already exists. break; } prev_end = next_end; } return 0; } int dirUnlinkHierarchy(const char* path) { struct stat st; DIR* dir; struct dirent* de; int fail = 0; /* is it a file or directory? */ if (lstat(path, &st) < 0) { return -1; } /* a file, so unlink it */ if (!S_ISDIR(st.st_mode)) { return unlink(path); } /* a directory, so open handle */ dir = opendir(path); if (dir == NULL) { return -1; } /* recurse over components */ errno = 0; while ((de = readdir(dir)) != NULL) { // TODO: don't blow the stack char dn[PATH_MAX]; if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) { continue; } snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name); if (dirUnlinkHierarchy(dn) < 0) { fail = 1; break; } errno = 0; } /* in case readdir or unlink_recursive failed */ if (fail || errno < 0) { int save = errno; closedir(dir); errno = save; return -1; } /* close directory handle */ if (closedir(dir) < 0) { return -1; } /* delete target directory */ return rmdir(path); }