From e662f34ef65a3ae94b4216097acc9f0a25611fac Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Sun, 11 Oct 2020 12:04:39 -0400 Subject: Eliminate CMake; flatten directory structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CMake is probably more trouble than it’s worth for this project. Replace it with a hand-rolled Ninja file. --- .gitignore | 8 +- .mailmap | 3 +- CMakeLists.txt | 53 --------- build.ninja | 42 +++++++ encoding.cc | 117 ++++++++++++++++++ encoding.h | 39 ++++++ fuse.h | 21 ++++ operations.cc | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++++ operations.h | 27 +++++ posix_extras.cc | 226 +++++++++++++++++++++++++++++++++++ posix_extras.h | 155 ++++++++++++++++++++++++ scoville.cc | 61 ++++++++++ src/CMakeLists.txt | 55 --------- src/encoding.cc | 117 ------------------ src/encoding.h | 39 ------ src/fuse.h | 21 ---- src/operations.cc | 334 ---------------------------------------------------- src/operations.h | 27 ----- src/posix_extras.cc | 226 ----------------------------------- src/posix_extras.h | 155 ------------------------ src/scoville.cc | 61 ---------- 21 files changed, 1028 insertions(+), 1093 deletions(-) delete mode 100644 CMakeLists.txt create mode 100644 build.ninja create mode 100644 encoding.cc create mode 100644 encoding.h create mode 100644 fuse.h create mode 100644 operations.cc create mode 100644 operations.h create mode 100644 posix_extras.cc create mode 100644 posix_extras.h create mode 100644 scoville.cc delete mode 100644 src/CMakeLists.txt delete mode 100644 src/encoding.cc delete mode 100644 src/encoding.h delete mode 100644 src/fuse.h delete mode 100644 src/operations.cc delete mode 100644 src/operations.h delete mode 100644 src/posix_extras.cc delete mode 100644 src/posix_extras.h delete mode 100644 src/scoville.cc diff --git a/.gitignore b/.gitignore index 995babd..723499b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,10 @@ \#* .\#* -# CMake -Debug/ -Release/ - +# Ninja +.ninja_* +*.o +scoville # Local Variables: # mode: conf diff --git a/.mailmap b/.mailmap index 9871667..c1e7839 100644 --- a/.mailmap +++ b/.mailmap @@ -1 +1,2 @@ - + + diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 5862df7..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2016 Benjamin Barenblat -# -# 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. - -cmake_policy(VERSION 3.0.2) -cmake_minimum_required(VERSION 3.0) - -project( - scoville - VERSION 0.0.0 - LANGUAGES CXX -) - -set(CMAKE_CXX_COMPILER clang++) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftrapv") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong --param=ssp-buffer-size=4") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything -Wno-c++98-compat") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-padded") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-weak-vtables") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-macros") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-cxa-atexit") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections") - -set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") # Use -O2, not -O3 - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bsymbolic-functions") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,now") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--hash-style=gnu") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-copy-dt-needed-entries") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") - -add_subdirectory(src) diff --git a/build.ninja b/build.ninja new file mode 100644 index 0000000..0b5be18 --- /dev/null +++ b/build.ninja @@ -0,0 +1,42 @@ +# Copyright 2020 Benjamin Barenblat +# +# 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. + +ninja_required_version = 1.3 + +cxx = clang++ +cflags = -O2 -DNDEBUG -D_FORTIFY_SOURCE -D_GLIBCXX_DEBUG $ + -D_GLIBCXX_DEBUG_PEDANTIC -std=c++14 -ftrapv -fno-strict-aliasing $ + -fstack-protector-strong --param=ssp-buffer-size=4 -Weverything $ + -Wno-c++98-compat -Wno-padded -Wno-weak-vtables -Wno-unused-macros -fPIE $ + -fno-rtti -fuse-cxa-atexit -pipe -ffunction-sections -fdata-sections $ + -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse +ldflags = -Wl,--gc-sections -Wl,-Bsymbolic-functions -pie -Wl,-z,now -Wl,-z,relro -Wl,--hash-style=gnu -Wl,--no-copy-dt-needed-entries -Wl,--as-needed -pthread +libs = -lfuse -lglog -lgflags + +rule cxx + command = $cxx -MMD -MT $out -MF $out.d $cflags -c $in -o $out + description = CXX $out + depfile = $out.d + deps = gcc + +rule link + command = $cxx $ldflags -o $out $in $libs + description = LINK $out + +build encoding.o: cxx encoding.cc +build operations.o: cxx operations.cc +build posix_extras.o: cxx posix_extras.cc +build scoville.o: cxx scoville.cc + +build scoville: link encoding.o operations.o posix_extras.o scoville.o diff --git a/encoding.cc b/encoding.cc new file mode 100644 index 0000000..702b2c4 --- /dev/null +++ b/encoding.cc @@ -0,0 +1,117 @@ +// Copyright 2016, 2018 Benjamin Barenblat +// +// 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 "encoding.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace scoville { + +namespace { + +void WriteAsciiAsHex(const char c, std::ostringstream* const out) { + if (1 < sizeof(c) && 0x100 <= c) { + // Not ASCII! + throw EncodingFailure("could not encode non-ASCII character '" + + std::string(1, c) + "'"); + } + *out << std::hex << static_cast(c); +} + +char ReadHexAsAscii(std::istringstream* const in) { + std::array hex_str; + in->get(hex_str.data(), hex_str.size()); + char* decoded_end; + const char result = + static_cast(std::strtol(hex_str.data(), &decoded_end, 16)); + if (decoded_end == hex_str.data()) { + throw DecodingFailure("could not decode invalid hex"); + } + return result; +} + +bool IsVfatBadCharacter(const char c) noexcept { + return (0 <= c && c < 0x20) || c == '*' || c == '?' || c == '<' || c == '>' || + c == '|' || c == '"' || c == ':' || c == '\\'; +} + +bool IsVfatBadLastCharacter(const char c) noexcept { + return IsVfatBadCharacter(c) || c == '.' || c == ' '; +} + +void EncodeStream(std::istringstream* const in, std::ostringstream* const out) { + char c; + while (!in->get(c).eof()) { + in->peek(); + const bool processing_last_character = in->eof(); + + if (IsVfatBadCharacter(c) || + (processing_last_character && IsVfatBadLastCharacter(c))) { + *out << '%'; + WriteAsciiAsHex(c, out); + } else if (c == '%') { + *out << "%%"; + } else { + *out << c; + } + } +} + +void DecodeStream(std::istringstream* const in, std::ostringstream* const out) { + char c; + while (!in->get(c).eof()) { + if (c == '%') { + if (in->peek() == '%') { + in->ignore(); + *out << "%"; + } else { + *out << ReadHexAsAscii(in); + } + } else { + *out << c; + } + } +} + +std::string TransformString( + std::function f, + const std::string& in) { + std::istringstream in_stream(in); + std::ostringstream out_stream; + f(&in_stream, &out_stream); + return out_stream.str(); +} + +} // namespace + +std::string Encode(const std::string& in) { + const std::string result = TransformString(EncodeStream, in); + VLOG(1) << "Encode: \"" << in << "\" -> \"" << result << "\""; + return result; +} + +std::string Decode(const std::string& in) { + const std::string result = TransformString(DecodeStream, in); + VLOG(1) << "Decode: \"" << in << "\" -> \"" << result << "\""; + return result; +} + +} // scoville diff --git a/encoding.h b/encoding.h new file mode 100644 index 0000000..d6e72ea --- /dev/null +++ b/encoding.h @@ -0,0 +1,39 @@ +// Copyright 2016 Benjamin Barenblat +// +// 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. + +#ifndef ENCODING_H_ +#define ENCODING_H_ + +#include +#include + +namespace scoville { + +class EncodingFailure : public std::logic_error { + public: + using std::logic_error::logic_error; +}; + +class DecodingFailure : public std::logic_error { + public: + using std::logic_error::logic_error; +}; + +std::string Encode(const std::string&); + +std::string Decode(const std::string&); + +} // scoville + +#endif // ENCODING_H_ diff --git a/fuse.h b/fuse.h new file mode 100644 index 0000000..f7d012f --- /dev/null +++ b/fuse.h @@ -0,0 +1,21 @@ +// Copyright 2016 Benjamin Barenblat +// +// 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. + +#ifndef FUSE_H_ +#define FUSE_H_ + +#define FUSE_USE_VERSION 26 +#include + +#endif // FUSE_H_ diff --git a/operations.cc b/operations.cc new file mode 100644 index 0000000..99817a1 --- /dev/null +++ b/operations.cc @@ -0,0 +1,334 @@ +// Copyright (C) 2001-2007 Miklos Szeredi +// Copyright (C) 2011 Sebastian Pipping +// Copyright (C) 2016 Benjamin Barenblat +// +// 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 "operations.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "encoding.h" +#include "fuse.h" +#include "posix_extras.h" + +namespace scoville { + +namespace { + +// Pointer to the directory underlying the mount point. +File* root_; + +mode_t DirectoryTypeToFileType(const unsigned char type) { + return static_cast(DTTOIF(type)); +} + +std::string MakeRelative(const std::string& path) { + if (path.at(0) != '/') { + throw std::system_error(ENOENT, std::system_category()); + } + return path.substr(1); +} + +void* Initialize(fuse_conn_info*) noexcept { return nullptr; } + +void Destroy(void*) noexcept {} + +int Statfs(const char* const c_path, struct statvfs* const output) { + const std::string path(Encode(c_path)); + if (path == "/") { + *output = root_->StatVFs(); + } else { + *output = + root_->OpenAt(MakeRelative(path).c_str(), O_RDONLY | O_PATH).StatVFs(); + } + return 0; +} + +int Getattr(const char* const c_path, struct stat* output) { + const std::string path(Encode(c_path)); + if (path == "/") { + *output = root_->Stat(); + } else { + *output = root_->LinkStatAt(MakeRelative(path).c_str()); + } + return 0; +} + +int Fgetattr(const char*, struct stat* const output, + struct fuse_file_info* const file_info) { + // This reinterpret_cast violates type aliasing rules, so a compiler may + // invoke undefined behavior if *output is ever dereferenced. However, we + // compile with -fno-strict-aliasing, so this should be safe. + *output = reinterpret_cast(file_info->fh)->Stat(); + return 0; +} + +template +int OpenResource(const std::string& path, const int flags, + uint64_t* const handle, const mode_t mode = 0) { + try { + std::unique_ptr t( + new T(path == "/" ? *root_ : root_->OpenAt(MakeRelative(path).c_str(), + flags, mode))); + static_assert(sizeof(*handle) == sizeof(std::uintptr_t), + "FUSE file handles are a different size than pointers"); + *handle = reinterpret_cast(t.release()); + return 0; + } catch (const std::bad_alloc&) { + return -ENOMEM; + } +} + +template +int ReleaseResource(const uint64_t handle) noexcept { + delete reinterpret_cast(handle); + return 0; +} + +int Mknod(const char* const c_path, const mode_t mode, const dev_t dev) { + const std::string path(Encode(c_path)); + if (path == "/") { + return -EISDIR; + } else { + root_->MkNod(MakeRelative(path).c_str(), mode, dev); + return 0; + } +} + +int Chmod(const char* const c_path, const mode_t mode) { + const std::string path(Encode(c_path)); + root_->ChModAt(path == "/" ? "." : MakeRelative(path).c_str(), mode); + return 0; +} + +int Rename(const char* const c_old_path, const char* const c_new_path) { + const std::string old_path(Encode(c_old_path)); + const std::string new_path(Encode(c_new_path)); + if (old_path == "/" || new_path == "/") { + return -EINVAL; + } else { + root_->RenameAt(MakeRelative(old_path).c_str(), + MakeRelative(new_path).c_str()); + return 0; + } +} + +int Create(const char* const path, const mode_t mode, + fuse_file_info* const file_info) { + return OpenResource(Encode(path), file_info->flags | O_CREAT, + &file_info->fh, mode); +} + +int Open(const char* const path, fuse_file_info* const file_info) { + return OpenResource(Encode(path), file_info->flags, &file_info->fh); +} + +int Read(const char*, char* const buffer, const size_t bytes, + const off_t offset, fuse_file_info* const file_info) { + // This reinterpret_cast violates type aliasing rules, so a compiler may + // invoke undefined behavior when file is dereferenced on the next line. + // However, we compile with -fno-strict-aliasing, so it should be safe. + auto* const file = reinterpret_cast(file_info->fh); + const std::vector read = file->Read(offset, bytes); + std::memcpy(buffer, read.data(), read.size()); + return static_cast(read.size()); +} + +int Write(const char*, const char* const buffer, const size_t bytes, + const off_t offset, fuse_file_info* const file_info) { + // See notes in Read about undefined behavior. + auto* const file = reinterpret_cast(file_info->fh); + const std::vector to_write(buffer, buffer + bytes); + file->Write(offset, to_write); + return static_cast(bytes); +} + +int Utimens(const char* const c_path, const timespec times[2]) { + const std::string path(Encode(c_path)); + root_->UTimeNs(path == "/" ? "." : MakeRelative(path).c_str(), times[0], + times[1]); + return 0; +} + +int Release(const char*, fuse_file_info* const file_info) { + return ReleaseResource(file_info->fh); +} + +int Unlink(const char* c_path) { + const std::string path(Encode(c_path)); + if (path == "/") { + // Removing the root is probably a bad idea. + return -EPERM; + } else { + root_->UnlinkAt(MakeRelative(path).c_str()); + return 0; + } +} + +int Symlink(const char*, const char*) { return -EPERM; } + +int Readlink(const char*, char*, size_t) { return -EINVAL; } + +int Mkdir(const char* const c_path, const mode_t mode) { + const std::string path(Encode(c_path)); + if (path == "/") { + // They're asking to create the mount point. Huh? + return -EEXIST; + } else { + root_->MkDir(MakeRelative(path).c_str(), mode); + return 0; + } +} + +int Opendir(const char* const path, fuse_file_info* const file_info) { + return OpenResource(Encode(path), O_DIRECTORY, &file_info->fh); +} + +int Readdir(const char*, void* const buffer, fuse_fill_dir_t filler, + const off_t offset, fuse_file_info* const file_info) { + // See notes in Read about undefined behavior. + auto* const directory = reinterpret_cast(file_info->fh); + + static_assert(std::is_same(), + "off_t is not convertible with long"); + if (offset != directory->offset()) { + directory->Seek(offset); + } + + for (std::experimental::optional entry = directory->ReadOne(); entry; + entry = directory->ReadOne()) { + struct stat stats; + std::memset(&stats, 0, sizeof(stats)); + stats.st_ino = entry->d_ino; + stats.st_mode = DirectoryTypeToFileType(entry->d_type); + const off_t next_offset = directory->offset(); + if (filler(buffer, Decode(entry->d_name).c_str(), &stats, next_offset)) { + break; + } + } + return 0; +} + +int Releasedir(const char*, fuse_file_info* const file_info) { + return ReleaseResource(file_info->fh); +} + +int Truncate(const char* const c_path, const off_t size) { + const std::string path(Encode(c_path)); + if (path == "/") { + return -EISDIR; + } else { + root_->OpenAt(MakeRelative(path).c_str(), O_WRONLY).Truncate(size); + return 0; + } +} + +int Ftruncate(const char*, const off_t size, fuse_file_info* const file_info) { + // See notes in Read about undefined behavior. + reinterpret_cast(file_info->fh)->Truncate(size); + return 0; +} + +int Rmdir(const char* c_path) { + const std::string path(Encode(c_path)); + if (path == "/") { + // Removing the root is probably a bad idea. + return -EPERM; + } else { + root_->RmDirAt(MakeRelative(path).c_str()); + return 0; + } +} + +template +int CatchAndReturnExceptions(Args... args) noexcept { + try { + return f(args...); + } catch (const std::system_error& e) { + return -e.code().value(); + } catch (...) { + LOG(ERROR) << "caught unexpected value"; + return -ENOTRECOVERABLE; + } +} + +} // namespace + +#define CATCH_AND_RETURN_EXCEPTIONS(f) CatchAndReturnExceptions + +fuse_operations FuseOperations(File* const root) { + root_ = root; + + fuse_operations result; + std::memset(&result, 0, sizeof(result)); + + result.flag_nullpath_ok = true; + result.flag_nopath = true; + result.flag_utime_omit_ok = true; + + result.init = Initialize; + result.destroy = Destroy; + + result.statfs = CATCH_AND_RETURN_EXCEPTIONS(Statfs); + + result.getattr = CATCH_AND_RETURN_EXCEPTIONS(Getattr); + result.fgetattr = CATCH_AND_RETURN_EXCEPTIONS(Fgetattr); + + result.mknod = CATCH_AND_RETURN_EXCEPTIONS(Mknod); + result.chmod = CATCH_AND_RETURN_EXCEPTIONS(Chmod); + result.rename = CATCH_AND_RETURN_EXCEPTIONS(Rename); + result.create = CATCH_AND_RETURN_EXCEPTIONS(Create); + result.open = CATCH_AND_RETURN_EXCEPTIONS(Open); + result.read = CATCH_AND_RETURN_EXCEPTIONS(Read); + result.write = CATCH_AND_RETURN_EXCEPTIONS(Write); + result.utimens = CATCH_AND_RETURN_EXCEPTIONS(Utimens); + result.release = CATCH_AND_RETURN_EXCEPTIONS(Release); + result.truncate = CATCH_AND_RETURN_EXCEPTIONS(Truncate); + result.ftruncate = CATCH_AND_RETURN_EXCEPTIONS(Ftruncate); + result.unlink = CATCH_AND_RETURN_EXCEPTIONS(Unlink); + + result.symlink = CATCH_AND_RETURN_EXCEPTIONS(Symlink); + result.readlink = CATCH_AND_RETURN_EXCEPTIONS(Readlink); + + result.mkdir = CATCH_AND_RETURN_EXCEPTIONS(Mkdir); + result.opendir = CATCH_AND_RETURN_EXCEPTIONS(Opendir); + result.readdir = CATCH_AND_RETURN_EXCEPTIONS(Readdir); + result.releasedir = CATCH_AND_RETURN_EXCEPTIONS(Releasedir); + result.rmdir = CATCH_AND_RETURN_EXCEPTIONS(Rmdir); + + return result; +} + +#undef CATCH_AND_RETURN_EXCEPTIONS + +} // namespace scoville diff --git a/operations.h b/operations.h new file mode 100644 index 0000000..33e0767 --- /dev/null +++ b/operations.h @@ -0,0 +1,27 @@ +// Copyright 2016 Benjamin Barenblat +// +// 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. + +#ifndef OPERATIONS_H_ +#define OPERATIONS_H_ + +#include "fuse.h" +#include "posix_extras.h" + +namespace scoville { + +fuse_operations FuseOperations(File* root); + +} // namespace scoville + +#endif // OPERATIONS_H_ diff --git a/posix_extras.cc b/posix_extras.cc new file mode 100644 index 0000000..3e916ad --- /dev/null +++ b/posix_extras.cc @@ -0,0 +1,226 @@ +// Copyright 2016 Benjamin Barenblat +// +// 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 "posix_extras.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scoville { + +namespace { + +std::system_error SystemError() { + return std::system_error(errno, std::system_category()); +} + +void ValidatePath(const char* const path) { + if (path[0] == '/') { + throw std::invalid_argument("absolute path"); + } +} + +template +T CheckSyscall(const T result) { + if (result == -1) { + throw SystemError(); + } + return result; +} + +} // namespace + +File::File(const char* const path, const int flags, const mode_t mode) + : path_(path) { + fd_ = CheckSyscall(open(path, flags, mode)); + VLOG(1) << "opening file descriptor " << fd_; +} + +File::File(const File& other) : path_(other.path_), fd_(other.Duplicate()) { + VLOG(1) << "opening file descriptor " << fd_; +} + +File::~File() noexcept { + VLOG(1) << "closing file descriptor " << fd_; + try { + CheckSyscall(close(fd_)); + } catch (...) { + LOG(ERROR) << "failed to close file descriptor " << fd_; + } +} + +struct stat File::Stat() const { + struct stat result; + CheckSyscall(fstat(fd_, &result)); + return result; +} + +void File::ChModAt(const char* const path, const mode_t mode) const { + ValidatePath(path); + CheckSyscall(fchmodat(fd_, path, mode, 0)); +} + +struct stat File::LinkStatAt(const char* const path) const { + ValidatePath(path); + struct stat result; + CheckSyscall(fstatat(fd_, path, &result, AT_SYMLINK_NOFOLLOW)); + return result; +} + +void File::MkDir(const char* const path, const mode_t mode) const { + ValidatePath(path); + CheckSyscall(mkdirat(fd_, path, mode | S_IFDIR)); +} + +void File::MkNod(const char* const path, const mode_t mode, + const dev_t dev) const { + ValidatePath(path); + CheckSyscall(mknodat(fd_, path, mode, dev)); +} + +File File::OpenAt(const char* const path, const int flags, + const mode_t mode) const { + ValidatePath(path); + File result; + result.fd_ = CheckSyscall(openat(fd_, path, flags, mode)); + result.path_ = path_ + "/" + path; + return result; +} + +std::vector File::Read(off_t offset, size_t bytes) const { + std::vector result(bytes, 0); + size_t cursor = 0; + ssize_t bytes_read; + while (0 < (bytes_read = CheckSyscall( + pread(fd_, result.data() + cursor, bytes, offset)))) { + cursor += static_cast(bytes_read); + offset += bytes_read; + bytes -= static_cast(bytes_read); + } + result.resize(cursor); + return result; +} + +std::string File::ReadLinkAt(const char* const path) const { + ValidatePath(path); + std::vector result(64, '\0'); + size_t bytes_read; + + while ((bytes_read = static_cast(CheckSyscall(readlinkat( + fd_, path, result.data(), result.size())))) == result.size()) { + // We filled the entire buffer. There may be more data we missed. + result.resize(result.size() * 2); + } + return std::string(result.data(), result.data() + bytes_read); +} + +void File::RenameAt(const char* old_path, const char* new_path) const { + ValidatePath(old_path); + ValidatePath(new_path); + CheckSyscall(renameat(fd_, old_path, fd_, new_path)); +} + +void File::RmDirAt(const char* const path) const { + ValidatePath(path); + CheckSyscall(unlinkat(fd_, path, AT_REMOVEDIR)); +} + +struct statvfs File::StatVFs() const { + struct statvfs result; + CheckSyscall(fstatvfs(fd_, &result)); + return result; +} + +void File::SymLinkAt(const char* const target, const char* const source) const { + ValidatePath(source); + CheckSyscall(symlinkat(target, fd_, source)); +} + +void File::Truncate(const off_t size) { CheckSyscall(ftruncate(fd_, size)); } + +void File::UnlinkAt(const char* const path) const { + ValidatePath(path); + CheckSyscall(unlinkat(fd_, path, 0)); +} + +void File::UTimeNs(const char* const path, const timespec& access, + const timespec& modification) const { + ValidatePath(path); + std::array times{{access, modification}}; + CheckSyscall(utimensat(fd_, path, times.data(), AT_SYMLINK_NOFOLLOW)); +} + +size_t File::Write(const off_t offset, + const std::vector& to_write) { + size_t bytes_written = 0; + while (bytes_written < to_write.size()) { + bytes_written += static_cast(CheckSyscall(pwrite( + fd_, to_write.data() + bytes_written, to_write.size() - bytes_written, + offset + static_cast(bytes_written)))); + } + return bytes_written; +} + +int File::Duplicate() const { return CheckSyscall(dup(fd_)); } + +Directory::Directory(const File& file) { + // "After a successful call to fdopendir(), fd is used internally by the + // implementation, and should not otherwise be used by the application." We + // therefore need to grab an unmanaged copy of the file descriptor from file. + if (!(stream_ = fdopendir(file.Duplicate()))) { + throw SystemError(); + } + rewinddir(stream_); +} + +Directory::~Directory() noexcept { + try { + CheckSyscall(closedir(stream_)); + } catch (...) { + LOG(ERROR) << "failed to close directory stream"; + } +} + +long Directory::offset() const { return CheckSyscall(telldir(stream_)); } + +void Directory::Seek(const long offset) noexcept { seekdir(stream_, offset); } + +std::experimental::optional Directory::ReadOne() { + dirent* result; + errno = 0; + if (!(result = readdir(stream_))) { + if (errno == 0) { + return std::experimental::nullopt; + } else { + throw SystemError(); + } + } + return *result; +} + +} // scoville diff --git a/posix_extras.h b/posix_extras.h new file mode 100644 index 0000000..9dec22d --- /dev/null +++ b/posix_extras.h @@ -0,0 +1,155 @@ +// Copyright 2016 Benjamin Barenblat +// +// 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. + +#ifndef POSIX_EXTRAS_H_ +#define POSIX_EXTRAS_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace scoville { + +class File; + +// RAII wrapper for Unix directory streams. +class Directory { + public: + explicit Directory(const File&); + virtual ~Directory() noexcept; + + long offset() const; + + void Seek(long) noexcept; + + std::experimental::optional ReadOne(); + + private: + Directory(const Directory&) = delete; + Directory(Directory&&) = delete; + + void operator=(const Directory&) = delete; + void operator=(Directory&&) = delete; + + DIR* stream_; +}; + +// RAII wrapper for Unix file descriptors. +class File { + public: + File(const char* path, int flags) : File(path, flags, 0777) {} + File(const char* path, int flags, mode_t mode); + File(const File&); + File(File&& other) = default; + virtual ~File() noexcept; + + const std::string& path() const noexcept { return path_; } + + // Calls fstat(2) on the file descriptor. + struct stat Stat() const; + + // Changes the file mode of the path relative to the file descriptor. The + // path must indeed be relative (i.e., it must not start with '/'). + void ChModAt(const char* path, mode_t) const; + + // Calls lstat(2) on the path relative to the file descriptor. The path must + // indeed be relative (i.e., it must not start with '/'). + struct stat LinkStatAt(const char* path) const; + + // Creates a directory at the path relative to the file descriptor. The path + // must indeed be relative (i.e., it must not start with '/'). + void MkDir(const char* path, mode_t mode) const; + + // Creates a file at the path relative to the file descriptor. The path must + // indeed be relative (i.e., it must not start with '/'). + void MkNod(const char* path, mode_t mode, dev_t dev) const; + + // Calls openat(2) on the path relative to the file descriptor. The path must + // indeed be relative (i.e., it must not start with '/'). + File OpenAt(const char* const path, const int flags) const { + return OpenAt(path, flags, 0); + } + File OpenAt(const char* path, int flags, mode_t mode) const; + + // Reads exactly the specified number of bytes from the file at the given + // offset, unless doing so would run past the end of the file, in which case + // fewer bytes are returned. + std::vector Read(off_t, size_t) const; + + // Reads the contents of a symbolic link. The path to the symbolic link is + // interpreted relative to the file descriptor and must indeed be relative + // (i.e., it must not start with '/'). + std::string ReadLinkAt(const char* path) const; + + // Renames a file from old_path to new_path. Both paths are interpreted + // relative to the file descriptor, and both must indeed be relative (i.e., + // they must not start with '/'). + void RenameAt(const char* old_path, const char* new_path) const; + + // Removes the directory at the path relative to the file descriptor. The + // path must indeed be relative (i.e., it must not start with '/'). + void RmDirAt(const char* path) const; + + // Retrieves information about the file system containing the referent of the + // file descriptor. + struct statvfs StatVFs() const; + + // Creates a symlink at source pointing to target. target is unvalidated. + // source is interpreted as a path relative to the file descriptor and must + // indeed be relative (i.e., it must not start with '/'). + void SymLinkAt(const char* target, const char* source) const; + + // Truncates the file to the specified size. + void Truncate(off_t); + + // Removes the file at the path relative to the file descriptor. The path + // must indeed be relative (i.e., it must not start with '/'). + void UnlinkAt(const char* path) const; + + // Sets the access and modification times of the file at the path relative to + // the file descriptor. The path must indeed be relative (i.e., it must not + // start with '/'). Does not follow symbolic links. + void UTimeNs(const char* path, const timespec& access, + const timespec& modification) const; + + // Writes the specified byte vector to the file at the given offset. Returns + // the number of bytes written, which will always be the number of bytes given + // as input. + size_t Write(off_t, const std::vector&); + + private: + File() {} + + void operator=(const File&) = delete; + void operator=(File&&) = delete; + + // Duplicates fd_ and returns the raw new file descriptor. + int Duplicate() const; + + std::string path_; + int fd_; + + friend Directory::Directory(const File&); +}; + +} // scoville + +#endif // POSIX_EXTRAS_H_ diff --git a/scoville.cc b/scoville.cc new file mode 100644 index 0000000..2eff02e --- /dev/null +++ b/scoville.cc @@ -0,0 +1,61 @@ +// Copyright (C) 2007, 2008 Jan Engelhardt +// Copyright (C) 2016 Benjamin Barenblat +// +// 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 +#include +#include + +#include +#include +#include + +#include "fuse.h" +#include "operations.h" +#include "posix_extras.h" + +constexpr char kUsage[] = R"(allow forbidden characters on VFAT file systems + +usage: scoville [flags] target_dir [-- fuse_options])"; + +int main(int argc, char* argv[]) { + google::InstallFailureSignalHandler(); + google::SetUsageMessage(kUsage); + google::ParseCommandLineFlags(&argc, &argv, true); + google::InitGoogleLogging(argv[0]); + + // This is an overlay file system, which means once we start FUSE, the + // underlying file system will be inaccessible through normal means. Open a + // file descriptor to the underlying root now so we can still do operations on + // it while it's overlayed. + std::unique_ptr root; + const char* const root_path = argv[argc - 1]; + try { + root.reset(new scoville::File(root_path, O_DIRECTORY)); + } catch (const std::system_error& e) { + LOG(FATAL) << "scoville: bad mount point `" << root_path + << "': " << e.what(); + } + LOG(INFO) << "overlaying " << root->path(); + const fuse_operations operations = scoville::FuseOperations(root.get()); + + // Add -o nonempty to argv so FUSE won't complain about overlaying. + char hyphen_o[] = "-o"; + char nonempty[] = "nonempty"; + std::vector new_argv(argv, argv + argc); + new_argv.emplace_back(hyphen_o); + new_argv.emplace_back(nonempty); + return fuse_main(new_argv.size(), new_argv.data(), &operations, nullptr); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 3f1fb15..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2016 Benjamin Barenblat -# -# 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(FindPkgConfig) - -pkg_check_modules(FUSE REQUIRED fuse) -pkg_check_modules(GFLAGS REQUIRED libgflags) -pkg_check_modules(GLOG REQUIRED libglog) - -link_directories( - ${FUSE_LIBRARY_DIRS} - ${GFLAGS_LIBRARY_DIRS} - ${GLOG_LIBRARY_DIRS} -) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FUSE_CFLAGS_OTHER}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GFLAGS_CFLAGS_OTHER}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GLOG_CFLAGS_OTHER}") - -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FUSE_LDFLAGS_OTHER}") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_LDFLAGS_OTHER}") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GLOG_LDFLAGS_OTHER}") - -add_executable( - scoville - encoding.cc - operations.cc - posix_extras.cc - scoville.cc -) - -target_include_directories( - scoville - SYSTEM PRIVATE ${FUSE_INCLUDE_DIRS} - SYSTEM PRIVATE ${GFLAGS_INCLUDE_DIRS} - SYSTEM PRIVATE ${GLOG_INCLUDE_DIRS} -) - -target_link_libraries( - scoville - ${FUSE_LIBRARIES} - ${GFLAGS_LIBRARIES} - ${GLOG_LIBRARIES} -) diff --git a/src/encoding.cc b/src/encoding.cc deleted file mode 100644 index 702b2c4..0000000 --- a/src/encoding.cc +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2016, 2018 Benjamin Barenblat -// -// 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 "encoding.h" - -#include -#include -#include -#include -#include -#include - -#include - -namespace scoville { - -namespace { - -void WriteAsciiAsHex(const char c, std::ostringstream* const out) { - if (1 < sizeof(c) && 0x100 <= c) { - // Not ASCII! - throw EncodingFailure("could not encode non-ASCII character '" + - std::string(1, c) + "'"); - } - *out << std::hex << static_cast(c); -} - -char ReadHexAsAscii(std::istringstream* const in) { - std::array hex_str; - in->get(hex_str.data(), hex_str.size()); - char* decoded_end; - const char result = - static_cast(std::strtol(hex_str.data(), &decoded_end, 16)); - if (decoded_end == hex_str.data()) { - throw DecodingFailure("could not decode invalid hex"); - } - return result; -} - -bool IsVfatBadCharacter(const char c) noexcept { - return (0 <= c && c < 0x20) || c == '*' || c == '?' || c == '<' || c == '>' || - c == '|' || c == '"' || c == ':' || c == '\\'; -} - -bool IsVfatBadLastCharacter(const char c) noexcept { - return IsVfatBadCharacter(c) || c == '.' || c == ' '; -} - -void EncodeStream(std::istringstream* const in, std::ostringstream* const out) { - char c; - while (!in->get(c).eof()) { - in->peek(); - const bool processing_last_character = in->eof(); - - if (IsVfatBadCharacter(c) || - (processing_last_character && IsVfatBadLastCharacter(c))) { - *out << '%'; - WriteAsciiAsHex(c, out); - } else if (c == '%') { - *out << "%%"; - } else { - *out << c; - } - } -} - -void DecodeStream(std::istringstream* const in, std::ostringstream* const out) { - char c; - while (!in->get(c).eof()) { - if (c == '%') { - if (in->peek() == '%') { - in->ignore(); - *out << "%"; - } else { - *out << ReadHexAsAscii(in); - } - } else { - *out << c; - } - } -} - -std::string TransformString( - std::function f, - const std::string& in) { - std::istringstream in_stream(in); - std::ostringstream out_stream; - f(&in_stream, &out_stream); - return out_stream.str(); -} - -} // namespace - -std::string Encode(const std::string& in) { - const std::string result = TransformString(EncodeStream, in); - VLOG(1) << "Encode: \"" << in << "\" -> \"" << result << "\""; - return result; -} - -std::string Decode(const std::string& in) { - const std::string result = TransformString(DecodeStream, in); - VLOG(1) << "Decode: \"" << in << "\" -> \"" << result << "\""; - return result; -} - -} // scoville diff --git a/src/encoding.h b/src/encoding.h deleted file mode 100644 index d6e72ea..0000000 --- a/src/encoding.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2016 Benjamin Barenblat -// -// 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. - -#ifndef ENCODING_H_ -#define ENCODING_H_ - -#include -#include - -namespace scoville { - -class EncodingFailure : public std::logic_error { - public: - using std::logic_error::logic_error; -}; - -class DecodingFailure : public std::logic_error { - public: - using std::logic_error::logic_error; -}; - -std::string Encode(const std::string&); - -std::string Decode(const std::string&); - -} // scoville - -#endif // ENCODING_H_ diff --git a/src/fuse.h b/src/fuse.h deleted file mode 100644 index f7d012f..0000000 --- a/src/fuse.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016 Benjamin Barenblat -// -// 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. - -#ifndef FUSE_H_ -#define FUSE_H_ - -#define FUSE_USE_VERSION 26 -#include - -#endif // FUSE_H_ diff --git a/src/operations.cc b/src/operations.cc deleted file mode 100644 index 99817a1..0000000 --- a/src/operations.cc +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright (C) 2001-2007 Miklos Szeredi -// Copyright (C) 2011 Sebastian Pipping -// Copyright (C) 2016 Benjamin Barenblat -// -// 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 "operations.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "encoding.h" -#include "fuse.h" -#include "posix_extras.h" - -namespace scoville { - -namespace { - -// Pointer to the directory underlying the mount point. -File* root_; - -mode_t DirectoryTypeToFileType(const unsigned char type) { - return static_cast(DTTOIF(type)); -} - -std::string MakeRelative(const std::string& path) { - if (path.at(0) != '/') { - throw std::system_error(ENOENT, std::system_category()); - } - return path.substr(1); -} - -void* Initialize(fuse_conn_info*) noexcept { return nullptr; } - -void Destroy(void*) noexcept {} - -int Statfs(const char* const c_path, struct statvfs* const output) { - const std::string path(Encode(c_path)); - if (path == "/") { - *output = root_->StatVFs(); - } else { - *output = - root_->OpenAt(MakeRelative(path).c_str(), O_RDONLY | O_PATH).StatVFs(); - } - return 0; -} - -int Getattr(const char* const c_path, struct stat* output) { - const std::string path(Encode(c_path)); - if (path == "/") { - *output = root_->Stat(); - } else { - *output = root_->LinkStatAt(MakeRelative(path).c_str()); - } - return 0; -} - -int Fgetattr(const char*, struct stat* const output, - struct fuse_file_info* const file_info) { - // This reinterpret_cast violates type aliasing rules, so a compiler may - // invoke undefined behavior if *output is ever dereferenced. However, we - // compile with -fno-strict-aliasing, so this should be safe. - *output = reinterpret_cast(file_info->fh)->Stat(); - return 0; -} - -template -int OpenResource(const std::string& path, const int flags, - uint64_t* const handle, const mode_t mode = 0) { - try { - std::unique_ptr t( - new T(path == "/" ? *root_ : root_->OpenAt(MakeRelative(path).c_str(), - flags, mode))); - static_assert(sizeof(*handle) == sizeof(std::uintptr_t), - "FUSE file handles are a different size than pointers"); - *handle = reinterpret_cast(t.release()); - return 0; - } catch (const std::bad_alloc&) { - return -ENOMEM; - } -} - -template -int ReleaseResource(const uint64_t handle) noexcept { - delete reinterpret_cast(handle); - return 0; -} - -int Mknod(const char* const c_path, const mode_t mode, const dev_t dev) { - const std::string path(Encode(c_path)); - if (path == "/") { - return -EISDIR; - } else { - root_->MkNod(MakeRelative(path).c_str(), mode, dev); - return 0; - } -} - -int Chmod(const char* const c_path, const mode_t mode) { - const std::string path(Encode(c_path)); - root_->ChModAt(path == "/" ? "." : MakeRelative(path).c_str(), mode); - return 0; -} - -int Rename(const char* const c_old_path, const char* const c_new_path) { - const std::string old_path(Encode(c_old_path)); - const std::string new_path(Encode(c_new_path)); - if (old_path == "/" || new_path == "/") { - return -EINVAL; - } else { - root_->RenameAt(MakeRelative(old_path).c_str(), - MakeRelative(new_path).c_str()); - return 0; - } -} - -int Create(const char* const path, const mode_t mode, - fuse_file_info* const file_info) { - return OpenResource(Encode(path), file_info->flags | O_CREAT, - &file_info->fh, mode); -} - -int Open(const char* const path, fuse_file_info* const file_info) { - return OpenResource(Encode(path), file_info->flags, &file_info->fh); -} - -int Read(const char*, char* const buffer, const size_t bytes, - const off_t offset, fuse_file_info* const file_info) { - // This reinterpret_cast violates type aliasing rules, so a compiler may - // invoke undefined behavior when file is dereferenced on the next line. - // However, we compile with -fno-strict-aliasing, so it should be safe. - auto* const file = reinterpret_cast(file_info->fh); - const std::vector read = file->Read(offset, bytes); - std::memcpy(buffer, read.data(), read.size()); - return static_cast(read.size()); -} - -int Write(const char*, const char* const buffer, const size_t bytes, - const off_t offset, fuse_file_info* const file_info) { - // See notes in Read about undefined behavior. - auto* const file = reinterpret_cast(file_info->fh); - const std::vector to_write(buffer, buffer + bytes); - file->Write(offset, to_write); - return static_cast(bytes); -} - -int Utimens(const char* const c_path, const timespec times[2]) { - const std::string path(Encode(c_path)); - root_->UTimeNs(path == "/" ? "." : MakeRelative(path).c_str(), times[0], - times[1]); - return 0; -} - -int Release(const char*, fuse_file_info* const file_info) { - return ReleaseResource(file_info->fh); -} - -int Unlink(const char* c_path) { - const std::string path(Encode(c_path)); - if (path == "/") { - // Removing the root is probably a bad idea. - return -EPERM; - } else { - root_->UnlinkAt(MakeRelative(path).c_str()); - return 0; - } -} - -int Symlink(const char*, const char*) { return -EPERM; } - -int Readlink(const char*, char*, size_t) { return -EINVAL; } - -int Mkdir(const char* const c_path, const mode_t mode) { - const std::string path(Encode(c_path)); - if (path == "/") { - // They're asking to create the mount point. Huh? - return -EEXIST; - } else { - root_->MkDir(MakeRelative(path).c_str(), mode); - return 0; - } -} - -int Opendir(const char* const path, fuse_file_info* const file_info) { - return OpenResource(Encode(path), O_DIRECTORY, &file_info->fh); -} - -int Readdir(const char*, void* const buffer, fuse_fill_dir_t filler, - const off_t offset, fuse_file_info* const file_info) { - // See notes in Read about undefined behavior. - auto* const directory = reinterpret_cast(file_info->fh); - - static_assert(std::is_same(), - "off_t is not convertible with long"); - if (offset != directory->offset()) { - directory->Seek(offset); - } - - for (std::experimental::optional entry = directory->ReadOne(); entry; - entry = directory->ReadOne()) { - struct stat stats; - std::memset(&stats, 0, sizeof(stats)); - stats.st_ino = entry->d_ino; - stats.st_mode = DirectoryTypeToFileType(entry->d_type); - const off_t next_offset = directory->offset(); - if (filler(buffer, Decode(entry->d_name).c_str(), &stats, next_offset)) { - break; - } - } - return 0; -} - -int Releasedir(const char*, fuse_file_info* const file_info) { - return ReleaseResource(file_info->fh); -} - -int Truncate(const char* const c_path, const off_t size) { - const std::string path(Encode(c_path)); - if (path == "/") { - return -EISDIR; - } else { - root_->OpenAt(MakeRelative(path).c_str(), O_WRONLY).Truncate(size); - return 0; - } -} - -int Ftruncate(const char*, const off_t size, fuse_file_info* const file_info) { - // See notes in Read about undefined behavior. - reinterpret_cast(file_info->fh)->Truncate(size); - return 0; -} - -int Rmdir(const char* c_path) { - const std::string path(Encode(c_path)); - if (path == "/") { - // Removing the root is probably a bad idea. - return -EPERM; - } else { - root_->RmDirAt(MakeRelative(path).c_str()); - return 0; - } -} - -template -int CatchAndReturnExceptions(Args... args) noexcept { - try { - return f(args...); - } catch (const std::system_error& e) { - return -e.code().value(); - } catch (...) { - LOG(ERROR) << "caught unexpected value"; - return -ENOTRECOVERABLE; - } -} - -} // namespace - -#define CATCH_AND_RETURN_EXCEPTIONS(f) CatchAndReturnExceptions - -fuse_operations FuseOperations(File* const root) { - root_ = root; - - fuse_operations result; - std::memset(&result, 0, sizeof(result)); - - result.flag_nullpath_ok = true; - result.flag_nopath = true; - result.flag_utime_omit_ok = true; - - result.init = Initialize; - result.destroy = Destroy; - - result.statfs = CATCH_AND_RETURN_EXCEPTIONS(Statfs); - - result.getattr = CATCH_AND_RETURN_EXCEPTIONS(Getattr); - result.fgetattr = CATCH_AND_RETURN_EXCEPTIONS(Fgetattr); - - result.mknod = CATCH_AND_RETURN_EXCEPTIONS(Mknod); - result.chmod = CATCH_AND_RETURN_EXCEPTIONS(Chmod); - result.rename = CATCH_AND_RETURN_EXCEPTIONS(Rename); - result.create = CATCH_AND_RETURN_EXCEPTIONS(Create); - result.open = CATCH_AND_RETURN_EXCEPTIONS(Open); - result.read = CATCH_AND_RETURN_EXCEPTIONS(Read); - result.write = CATCH_AND_RETURN_EXCEPTIONS(Write); - result.utimens = CATCH_AND_RETURN_EXCEPTIONS(Utimens); - result.release = CATCH_AND_RETURN_EXCEPTIONS(Release); - result.truncate = CATCH_AND_RETURN_EXCEPTIONS(Truncate); - result.ftruncate = CATCH_AND_RETURN_EXCEPTIONS(Ftruncate); - result.unlink = CATCH_AND_RETURN_EXCEPTIONS(Unlink); - - result.symlink = CATCH_AND_RETURN_EXCEPTIONS(Symlink); - result.readlink = CATCH_AND_RETURN_EXCEPTIONS(Readlink); - - result.mkdir = CATCH_AND_RETURN_EXCEPTIONS(Mkdir); - result.opendir = CATCH_AND_RETURN_EXCEPTIONS(Opendir); - result.readdir = CATCH_AND_RETURN_EXCEPTIONS(Readdir); - result.releasedir = CATCH_AND_RETURN_EXCEPTIONS(Releasedir); - result.rmdir = CATCH_AND_RETURN_EXCEPTIONS(Rmdir); - - return result; -} - -#undef CATCH_AND_RETURN_EXCEPTIONS - -} // namespace scoville diff --git a/src/operations.h b/src/operations.h deleted file mode 100644 index 33e0767..0000000 --- a/src/operations.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2016 Benjamin Barenblat -// -// 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. - -#ifndef OPERATIONS_H_ -#define OPERATIONS_H_ - -#include "fuse.h" -#include "posix_extras.h" - -namespace scoville { - -fuse_operations FuseOperations(File* root); - -} // namespace scoville - -#endif // OPERATIONS_H_ diff --git a/src/posix_extras.cc b/src/posix_extras.cc deleted file mode 100644 index 3e916ad..0000000 --- a/src/posix_extras.cc +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2016 Benjamin Barenblat -// -// 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 "posix_extras.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace scoville { - -namespace { - -std::system_error SystemError() { - return std::system_error(errno, std::system_category()); -} - -void ValidatePath(const char* const path) { - if (path[0] == '/') { - throw std::invalid_argument("absolute path"); - } -} - -template -T CheckSyscall(const T result) { - if (result == -1) { - throw SystemError(); - } - return result; -} - -} // namespace - -File::File(const char* const path, const int flags, const mode_t mode) - : path_(path) { - fd_ = CheckSyscall(open(path, flags, mode)); - VLOG(1) << "opening file descriptor " << fd_; -} - -File::File(const File& other) : path_(other.path_), fd_(other.Duplicate()) { - VLOG(1) << "opening file descriptor " << fd_; -} - -File::~File() noexcept { - VLOG(1) << "closing file descriptor " << fd_; - try { - CheckSyscall(close(fd_)); - } catch (...) { - LOG(ERROR) << "failed to close file descriptor " << fd_; - } -} - -struct stat File::Stat() const { - struct stat result; - CheckSyscall(fstat(fd_, &result)); - return result; -} - -void File::ChModAt(const char* const path, const mode_t mode) const { - ValidatePath(path); - CheckSyscall(fchmodat(fd_, path, mode, 0)); -} - -struct stat File::LinkStatAt(const char* const path) const { - ValidatePath(path); - struct stat result; - CheckSyscall(fstatat(fd_, path, &result, AT_SYMLINK_NOFOLLOW)); - return result; -} - -void File::MkDir(const char* const path, const mode_t mode) const { - ValidatePath(path); - CheckSyscall(mkdirat(fd_, path, mode | S_IFDIR)); -} - -void File::MkNod(const char* const path, const mode_t mode, - const dev_t dev) const { - ValidatePath(path); - CheckSyscall(mknodat(fd_, path, mode, dev)); -} - -File File::OpenAt(const char* const path, const int flags, - const mode_t mode) const { - ValidatePath(path); - File result; - result.fd_ = CheckSyscall(openat(fd_, path, flags, mode)); - result.path_ = path_ + "/" + path; - return result; -} - -std::vector File::Read(off_t offset, size_t bytes) const { - std::vector result(bytes, 0); - size_t cursor = 0; - ssize_t bytes_read; - while (0 < (bytes_read = CheckSyscall( - pread(fd_, result.data() + cursor, bytes, offset)))) { - cursor += static_cast(bytes_read); - offset += bytes_read; - bytes -= static_cast(bytes_read); - } - result.resize(cursor); - return result; -} - -std::string File::ReadLinkAt(const char* const path) const { - ValidatePath(path); - std::vector result(64, '\0'); - size_t bytes_read; - - while ((bytes_read = static_cast(CheckSyscall(readlinkat( - fd_, path, result.data(), result.size())))) == result.size()) { - // We filled the entire buffer. There may be more data we missed. - result.resize(result.size() * 2); - } - return std::string(result.data(), result.data() + bytes_read); -} - -void File::RenameAt(const char* old_path, const char* new_path) const { - ValidatePath(old_path); - ValidatePath(new_path); - CheckSyscall(renameat(fd_, old_path, fd_, new_path)); -} - -void File::RmDirAt(const char* const path) const { - ValidatePath(path); - CheckSyscall(unlinkat(fd_, path, AT_REMOVEDIR)); -} - -struct statvfs File::StatVFs() const { - struct statvfs result; - CheckSyscall(fstatvfs(fd_, &result)); - return result; -} - -void File::SymLinkAt(const char* const target, const char* const source) const { - ValidatePath(source); - CheckSyscall(symlinkat(target, fd_, source)); -} - -void File::Truncate(const off_t size) { CheckSyscall(ftruncate(fd_, size)); } - -void File::UnlinkAt(const char* const path) const { - ValidatePath(path); - CheckSyscall(unlinkat(fd_, path, 0)); -} - -void File::UTimeNs(const char* const path, const timespec& access, - const timespec& modification) const { - ValidatePath(path); - std::array times{{access, modification}}; - CheckSyscall(utimensat(fd_, path, times.data(), AT_SYMLINK_NOFOLLOW)); -} - -size_t File::Write(const off_t offset, - const std::vector& to_write) { - size_t bytes_written = 0; - while (bytes_written < to_write.size()) { - bytes_written += static_cast(CheckSyscall(pwrite( - fd_, to_write.data() + bytes_written, to_write.size() - bytes_written, - offset + static_cast(bytes_written)))); - } - return bytes_written; -} - -int File::Duplicate() const { return CheckSyscall(dup(fd_)); } - -Directory::Directory(const File& file) { - // "After a successful call to fdopendir(), fd is used internally by the - // implementation, and should not otherwise be used by the application." We - // therefore need to grab an unmanaged copy of the file descriptor from file. - if (!(stream_ = fdopendir(file.Duplicate()))) { - throw SystemError(); - } - rewinddir(stream_); -} - -Directory::~Directory() noexcept { - try { - CheckSyscall(closedir(stream_)); - } catch (...) { - LOG(ERROR) << "failed to close directory stream"; - } -} - -long Directory::offset() const { return CheckSyscall(telldir(stream_)); } - -void Directory::Seek(const long offset) noexcept { seekdir(stream_, offset); } - -std::experimental::optional Directory::ReadOne() { - dirent* result; - errno = 0; - if (!(result = readdir(stream_))) { - if (errno == 0) { - return std::experimental::nullopt; - } else { - throw SystemError(); - } - } - return *result; -} - -} // scoville diff --git a/src/posix_extras.h b/src/posix_extras.h deleted file mode 100644 index 9dec22d..0000000 --- a/src/posix_extras.h +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2016 Benjamin Barenblat -// -// 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. - -#ifndef POSIX_EXTRAS_H_ -#define POSIX_EXTRAS_H_ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace scoville { - -class File; - -// RAII wrapper for Unix directory streams. -class Directory { - public: - explicit Directory(const File&); - virtual ~Directory() noexcept; - - long offset() const; - - void Seek(long) noexcept; - - std::experimental::optional ReadOne(); - - private: - Directory(const Directory&) = delete; - Directory(Directory&&) = delete; - - void operator=(const Directory&) = delete; - void operator=(Directory&&) = delete; - - DIR* stream_; -}; - -// RAII wrapper for Unix file descriptors. -class File { - public: - File(const char* path, int flags) : File(path, flags, 0777) {} - File(const char* path, int flags, mode_t mode); - File(const File&); - File(File&& other) = default; - virtual ~File() noexcept; - - const std::string& path() const noexcept { return path_; } - - // Calls fstat(2) on the file descriptor. - struct stat Stat() const; - - // Changes the file mode of the path relative to the file descriptor. The - // path must indeed be relative (i.e., it must not start with '/'). - void ChModAt(const char* path, mode_t) const; - - // Calls lstat(2) on the path relative to the file descriptor. The path must - // indeed be relative (i.e., it must not start with '/'). - struct stat LinkStatAt(const char* path) const; - - // Creates a directory at the path relative to the file descriptor. The path - // must indeed be relative (i.e., it must not start with '/'). - void MkDir(const char* path, mode_t mode) const; - - // Creates a file at the path relative to the file descriptor. The path must - // indeed be relative (i.e., it must not start with '/'). - void MkNod(const char* path, mode_t mode, dev_t dev) const; - - // Calls openat(2) on the path relative to the file descriptor. The path must - // indeed be relative (i.e., it must not start with '/'). - File OpenAt(const char* const path, const int flags) const { - return OpenAt(path, flags, 0); - } - File OpenAt(const char* path, int flags, mode_t mode) const; - - // Reads exactly the specified number of bytes from the file at the given - // offset, unless doing so would run past the end of the file, in which case - // fewer bytes are returned. - std::vector Read(off_t, size_t) const; - - // Reads the contents of a symbolic link. The path to the symbolic link is - // interpreted relative to the file descriptor and must indeed be relative - // (i.e., it must not start with '/'). - std::string ReadLinkAt(const char* path) const; - - // Renames a file from old_path to new_path. Both paths are interpreted - // relative to the file descriptor, and both must indeed be relative (i.e., - // they must not start with '/'). - void RenameAt(const char* old_path, const char* new_path) const; - - // Removes the directory at the path relative to the file descriptor. The - // path must indeed be relative (i.e., it must not start with '/'). - void RmDirAt(const char* path) const; - - // Retrieves information about the file system containing the referent of the - // file descriptor. - struct statvfs StatVFs() const; - - // Creates a symlink at source pointing to target. target is unvalidated. - // source is interpreted as a path relative to the file descriptor and must - // indeed be relative (i.e., it must not start with '/'). - void SymLinkAt(const char* target, const char* source) const; - - // Truncates the file to the specified size. - void Truncate(off_t); - - // Removes the file at the path relative to the file descriptor. The path - // must indeed be relative (i.e., it must not start with '/'). - void UnlinkAt(const char* path) const; - - // Sets the access and modification times of the file at the path relative to - // the file descriptor. The path must indeed be relative (i.e., it must not - // start with '/'). Does not follow symbolic links. - void UTimeNs(const char* path, const timespec& access, - const timespec& modification) const; - - // Writes the specified byte vector to the file at the given offset. Returns - // the number of bytes written, which will always be the number of bytes given - // as input. - size_t Write(off_t, const std::vector&); - - private: - File() {} - - void operator=(const File&) = delete; - void operator=(File&&) = delete; - - // Duplicates fd_ and returns the raw new file descriptor. - int Duplicate() const; - - std::string path_; - int fd_; - - friend Directory::Directory(const File&); -}; - -} // scoville - -#endif // POSIX_EXTRAS_H_ diff --git a/src/scoville.cc b/src/scoville.cc deleted file mode 100644 index 2eff02e..0000000 --- a/src/scoville.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2007, 2008 Jan Engelhardt -// Copyright (C) 2016 Benjamin Barenblat -// -// 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 -#include -#include - -#include -#include -#include - -#include "fuse.h" -#include "operations.h" -#include "posix_extras.h" - -constexpr char kUsage[] = R"(allow forbidden characters on VFAT file systems - -usage: scoville [flags] target_dir [-- fuse_options])"; - -int main(int argc, char* argv[]) { - google::InstallFailureSignalHandler(); - google::SetUsageMessage(kUsage); - google::ParseCommandLineFlags(&argc, &argv, true); - google::InitGoogleLogging(argv[0]); - - // This is an overlay file system, which means once we start FUSE, the - // underlying file system will be inaccessible through normal means. Open a - // file descriptor to the underlying root now so we can still do operations on - // it while it's overlayed. - std::unique_ptr root; - const char* const root_path = argv[argc - 1]; - try { - root.reset(new scoville::File(root_path, O_DIRECTORY)); - } catch (const std::system_error& e) { - LOG(FATAL) << "scoville: bad mount point `" << root_path - << "': " << e.what(); - } - LOG(INFO) << "overlaying " << root->path(); - const fuse_operations operations = scoville::FuseOperations(root.get()); - - // Add -o nonempty to argv so FUSE won't complain about overlaying. - char hyphen_o[] = "-o"; - char nonempty[] = "nonempty"; - std::vector new_argv(argv, argv + argc); - new_argv.emplace_back(hyphen_o); - new_argv.emplace_back(nonempty); - return fuse_main(new_argv.size(), new_argv.data(), &operations, nullptr); -} -- cgit v1.2.3