// ---------------------------------------------------------------------- // File: FileUtils.cc // Author: Georgios Bitzes - CERN // ---------------------------------------------------------------------- /************************************************************************ * quarkdb - a redis-like highly available key-value store * * Copyright (C) 2016 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "utils/DirectoryIterator.hh" #include "utils/FileUtils.hh" #include "Utils.hh" #include #include namespace quarkdb { std::string pathJoin(std::string_view part1, std::string_view part2) { if(part1.empty()) return SSTR("/" << part2); if(part2.empty()) return SSTR(part1); if(part1[part1.size()-1] == '/') return SSTR(part1 << part2); return SSTR(part1 << "/" << part2); } bool mkpath(const std::string &path, mode_t mode, std::string &err) { size_t pos = path.find("/"); while( (pos = path.find("/", pos+1)) != std::string::npos) { std::string chunk = path.substr(0, pos); struct stat sb; if(stat(chunk.c_str(), &sb) != 0) { qdb_info("Creating directory: " << chunk); if(mkdir(chunk.c_str(), mode) < 0) { int localerrno = errno; err = SSTR("cannot create directory " << chunk << ": " << strerror(localerrno)); return false; } } } return true; } void mkpath_or_die(const std::string &path, mode_t mode) { std::string err; if(!quarkdb::mkpath(path, mode, err)) qdb_throw(err); } bool directoryExists(const std::string &path, std::string &err) { struct stat sb; if(stat(path.c_str(), &sb) != 0) { err = SSTR("Cannot stat " << path); return false; } if(!S_ISDIR(sb.st_mode)) { err = SSTR(path << " is not a directory"); return false; } return true; } bool fileExists(const std::string &path, std::string &err) { struct stat sb; if(stat(path.c_str(), &sb) != 0) { err = SSTR("Cannot stat " << path); return false; } if(!S_ISREG(sb.st_mode)) { err = SSTR(path << " is not a directory"); return false; } return true; } bool readFile(FILE *f, std::string &contents) { bool retvalue = true; std::ostringstream ss; const int BUFFER_SIZE = 1024; char buffer[BUFFER_SIZE]; while(true) { size_t bytesRead = fread(buffer, 1, BUFFER_SIZE, f); if(bytesRead > 0) { ss.write(buffer, bytesRead); } // end of file if(bytesRead != BUFFER_SIZE) { retvalue = feof(f); break; } } contents = ss.str(); return retvalue; } bool readFile(const std::string &path, std::string &contents) { bool retvalue = true; FILE *in = fopen(path.c_str(), "rb"); if(!in) { return false; } retvalue = readFile(in, contents); fclose(in); return retvalue; } bool readPasswordFile(const std::string &path, std::string &contents) { bool retvalue = true; FILE *in = fopen(path.c_str(), "rb"); if(!in) { qdb_warn("Could not open " << path); return false; } // Ensure file permissions are 400. struct stat sb; if(fstat(fileno(in), &sb) != 0) { fclose(in); qdb_warn("Could not fstat " << path << " after opening (should never happen?!)"); return false; } if(!areFilePermissionsSecure(sb.st_mode)) { qdb_warn("Refusing to read " << path << ", bad file permissions, should be 0400."); fclose(in); return false; } retvalue = readFile(in, contents); fclose(in); if(retvalue) { // Right trim any newlines and whitespace. By far the most common case will // be to have a single line in the password file. Users will expect to be // able to copy/paste that, let's not complicate matters with newlines. contents.erase(contents.find_last_not_of(" \t\n\r\f\v") + 1); } return retvalue; } bool areFilePermissionsSecure(mode_t mode) { if ((mode & 0077) != 0) { // Should disallow access to other users/groups return false; } if ((mode & 0700) != 0400) { // Just read access for user return false; } return true; } bool write_file(std::string_view path, std::string_view contents, std::string &err) { bool retvalue; FILE *out = fopen(std::string(path).c_str(), "wb"); if(!out) { err = SSTR("Unable to open path for writing: " << path << ", errno: " << errno); return false; } retvalue = fwrite(contents.data(), sizeof(char), contents.size(), out); fclose(out); return retvalue; } void write_file_or_die(std::string_view path, std::string_view contents) { std::string err; if(!write_file(path, contents, err)) { qdb_throw(err); } } void rename_directory_or_die(const std::string &source, const std::string &destination) { qdb_info("Renaming directory: '" << source << "' to '" << destination << "'"); std::string tmp; if(!directoryExists(source, tmp)) { qdb_throw("Tried to rename '" << source << "' to '" << destination << "', but '" << source << "' did not exist."); } int ret = rename(source.c_str(), destination.c_str()); if(ret != 0) { qdb_throw("Tried to rename '" << source << "' to '" << destination << "', but ::rename failed: " << strerror(errno)); } } bool countFilesInDirectoryRecursively(const std::string &path, std::string &err, size_t &nfiles) { size_t retval = 0; DirectoryIterator iterator(path); struct dirent *entry; while( (entry = iterator.next()) ) { if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; std::string currentPath = SSTR(path << "/" << entry->d_name); if(entry->d_type == DT_DIR) { size_t dirfiles = 0; if(!countFilesInDirectoryRecursively(currentPath, err, dirfiles)) { return false; } retval += dirfiles; } else { retval++; } } if(!iterator.ok()) { err = SSTR("countFilesInDirectoryRecursively failed, unable to iterate directory: " << iterator.err()); return false; } nfiles = retval; return true; } Status ensureSameFilesystem(std::string_view path1, std::string_view path2) { struct stat stat1, stat2; if(stat(std::string(path1).c_str(), &stat1) != 0) { return Status(EINVAL, SSTR("cannot stat " << path1 << ": " << errno)); } if(stat(std::string(path2).c_str(), &stat2) != 0) { return Status(EINVAL, SSTR("cannot stat " << path2 << ": " << errno)); } if(stat1.st_dev != stat2.st_dev) { return Status(EINVAL, SSTR("paths not on the same filesystem")); } return Status(); } }