Created
September 4, 2023 04:22
-
-
Save tchatow/7751287f218a0cc39b2038a8e04c704d to your computer and use it in GitHub Desktop.
FUSE application to mount raw disk images and access partitions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 <https://www.gnu.org/licenses/>. | |
*/ | |
#define FUSE_USE_VERSION 31 | |
#include <libfdisk/libfdisk.h> | |
#include <sys/mman.h> | |
#include <unistd.h> | |
#include <charconv> | |
#include <optional> | |
#include <span> | |
#include "absl/strings/str_cat.h" | |
#include "fuse.h" | |
#include "gflags.h" | |
#include "glog/logging.h" | |
DEFINE_string(i, "", "Partition image"); | |
DEFINE_bool(fg, false, "Pass -f to fuse"); | |
class FdiskContext { | |
public: | |
static int OpenFile(const char* path) { | |
int fd = open(path, O_RDONLY); | |
PCHECK(fd != -1); | |
return fd; | |
} | |
static off64_t GetLen(int fd) { | |
const off64_t len = lseek64(fd, 0, SEEK_END); | |
lseek64(fd, 0, SEEK_SET); | |
return len; | |
} | |
FdiskContext(const char* path) | |
: fd_(OpenFile(path)), | |
len_(GetLen(fd_)), | |
data_(reinterpret_cast<const uint8_t*>(CHECK_NOTNULL( | |
mmap(nullptr, len_, PROT_READ, MAP_PRIVATE, fd_, 0)))), | |
ctx_(CHECK_NOTNULL(fdisk_new_context())) { | |
CHECK_EQ(fdisk_assign_device(ctx_, path, 1), 0); | |
CHECK_EQ(fdisk_get_partitions(ctx_, &table_), 0); | |
const size_t num_partitions = fdisk_table_get_nents(table_); | |
num_partitions_ = num_partitions; | |
const auto sector_size = fdisk_get_sector_size(ctx_); | |
LOG(INFO) << "Image size: " << len_; | |
LOG(INFO) << "Found " << num_partitions << " partitions"; | |
LOG(INFO) << "Sector size: " << sector_size << " bytes"; | |
for (size_t i = 0; i < num_partitions; ++i) { | |
fdisk_partition* part = | |
CHECK_NOTNULL(fdisk_table_get_partition(table_, i)); | |
if (fdisk_partition_has_start(part) && fdisk_partition_has_size(part)) { | |
const uint64_t start = fdisk_partition_get_start(part) * sector_size; | |
const uint64_t len = fdisk_partition_get_size(part) * sector_size; | |
LOG(INFO) << "Part " << start << " sz " << len << " end " | |
<< (start + len); | |
CHECK(start + len < len_); | |
partitions_.emplace_back(data_ + start, len); | |
} | |
} | |
} | |
~FdiskContext() { | |
fdisk_unref_table(table_); | |
fdisk_unref_context(ctx_); | |
} | |
size_t num_partitions() const { return num_partitions_; } | |
size_t get_part_len(int idx) const { | |
CHECK(idx >= 0 && idx < static_cast<int>(num_partitions_)); | |
return partitions_[idx].size(); | |
} | |
std::span<const uint8_t> get_part(int idx) const { | |
CHECK(idx >= 0 && idx < static_cast<int>(num_partitions_)); | |
return partitions_[idx]; | |
} | |
private: | |
const int fd_; | |
size_t num_partitions_ = 0; | |
const uint64_t len_; | |
const uint8_t* data_; | |
fdisk_context* ctx_; | |
fdisk_table* table_; | |
std::vector<std::span<const uint8_t>> partitions_; | |
}; | |
static std::optional<int> ParseInt(std::string_view str) { | |
int parsed_value; | |
const char* start = str.data(); | |
const char* end = str.data() + str.size(); | |
std::from_chars_result result = std::from_chars(start, end, parsed_value); | |
if (result.ptr == end && result.ec == std::errc()) { | |
return parsed_value; | |
} else { | |
return std::nullopt; | |
} | |
} | |
class PartitionFS { | |
public: | |
PartitionFS(const char* img) : info_(img) {} | |
std::optional<int> ParseAndValidatePath(const char* path) { | |
std::string_view sv(path); | |
if (!sv.starts_with("/part")) { | |
return std::nullopt; | |
} | |
sv = sv.substr(5); | |
auto idx = ParseInt(sv); | |
if (!idx.has_value() || idx >= info_.num_partitions()) { | |
return std::nullopt; | |
} | |
return idx; | |
} | |
static int do_getattr(const char* path, struct stat* stbuf, | |
struct fuse_file_info*) { | |
const auto fs = | |
reinterpret_cast<PartitionFS*>(fuse_get_context()->private_data); | |
memset(stbuf, 0, sizeof(struct stat)); | |
if (strcmp(path, "/") == 0) { | |
stbuf->st_mode = S_IFDIR | 0755; | |
stbuf->st_nlink = 1; | |
} else { | |
auto search = fs->ParseAndValidatePath(path); | |
if (!search.has_value()) { | |
return -ENOENT; | |
} | |
stbuf->st_mode = S_IFREG | 0444; | |
stbuf->st_nlink = 1; | |
stbuf->st_size = fs->info_.get_part_len(*search); | |
} | |
stbuf->st_uid = fs->uid_; | |
stbuf->st_gid = fs->gid_; | |
return 0; | |
} | |
static int do_readdir(const char* path, void* buf, fuse_fill_dir_t filler, | |
off_t offset, struct fuse_file_info* fi, | |
enum fuse_readdir_flags flags) { | |
const auto fs = | |
reinterpret_cast<PartitionFS*>(fuse_get_context()->private_data); | |
(void)offset; | |
(void)fi; | |
(void)flags; | |
if (strcmp(path, "/") != 0) { | |
return -ENOENT; | |
} | |
constexpr fuse_fill_dir_flags eflags = static_cast<fuse_fill_dir_flags>(0); | |
filler(buf, ".", NULL, 0, eflags); | |
filler(buf, "..", NULL, 0, eflags); | |
for (size_t i = 0; i < fs->info_.num_partitions(); ++i) { | |
filler(buf, absl::StrCat("part", i).c_str(), NULL, 0, eflags); | |
} | |
return 0; | |
} | |
static int do_open(const char* path, struct fuse_file_info* fi) { | |
const auto fs = | |
reinterpret_cast<PartitionFS*>(fuse_get_context()->private_data); | |
auto search = fs->ParseAndValidatePath(path); | |
if (!search.has_value()) { | |
return -ENOENT; | |
} | |
// Check read only | |
if ((fi->flags & O_ACCMODE) != O_RDONLY) { | |
return -EACCES; | |
} | |
return 0; | |
} | |
static int do_read(const char* path, char* buf, size_t size, off_t offset, | |
struct fuse_file_info* fi) { | |
(void)fi; | |
const auto fs = | |
reinterpret_cast<PartitionFS*>(fuse_get_context()->private_data); | |
auto search = fs->ParseAndValidatePath(path); | |
if (!search.has_value()) { | |
return -ENOENT; | |
} | |
const auto sp = fs->info_.get_part(*search); | |
const size_t len = sp.size(); | |
if (offset < static_cast<int64_t>(len)) { | |
if (offset + size > len) { | |
size = len - offset; | |
} | |
memcpy(buf, sp.data() + offset, size); | |
} else { | |
size = 0; | |
} | |
return size; | |
} | |
static const fuse_operations* get_operations() { | |
static const fuse_operations ops{ | |
.getattr = do_getattr, | |
.open = do_open, | |
.read = do_read, | |
.readdir = do_readdir, | |
}; | |
return &ops; | |
} | |
private: | |
const uid_t uid_ = getuid(); | |
const gid_t gid_ = getgid(); | |
FdiskContext info_; | |
}; | |
int main(int argc, char** argv) { | |
google::InitGoogleLogging(argv[0]); | |
google::ParseCommandLineFlags(&argc, &argv, true); | |
FLAGS_logtostderr = true; | |
CHECK(!FLAGS_i.empty()); | |
// CHECK(!FLAGS_m.empty()); | |
/*if (FLAGS_fhelp) { | |
google::AppendFlagsIntoFile() | |
}*/ | |
std::vector<char*> new_argv; | |
for (int i = 0; i < argc; ++i) { | |
new_argv.push_back(argv[i]); | |
} | |
if (FLAGS_fg) { | |
static char fgstr[3] = "-f"; | |
new_argv.push_back(fgstr); | |
} | |
PartitionFS part_fs(FLAGS_i.c_str()); | |
fuse_main(new_argv.size(), new_argv.data(), part_fs.get_operations(), | |
&part_fs); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment