Skip to content

Instantly share code, notes, and snippets.

@tchatow
Created September 4, 2023 04:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tchatow/7751287f218a0cc39b2038a8e04c704d to your computer and use it in GitHub Desktop.
Save tchatow/7751287f218a0cc39b2038a8e04c704d to your computer and use it in GitHub Desktop.
FUSE application to mount raw disk images and access partitions.
/*
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