mirror of
https://github.com/mhx/dwarfs.git
synced 2025-09-07 19:41:54 -04:00
Add --chmod option (fixes gh #7)
This commit is contained in:
parent
18353dc954
commit
dac9861a55
@ -309,6 +309,7 @@ list(
|
||||
src/dwarfs/block_manager.cpp
|
||||
src/dwarfs/builtin_script.cpp
|
||||
src/dwarfs/checksum.cpp
|
||||
src/dwarfs/chmod_transformer.cpp
|
||||
src/dwarfs/console_writer.cpp
|
||||
src/dwarfs/entry.cpp
|
||||
src/dwarfs/error.cpp
|
||||
|
@ -235,6 +235,14 @@ Most other options are concerned with compression tuning:
|
||||
entirely possible. Moving from second to minute resolution, for example,
|
||||
will save roughly 6 bits per file system entry in the metadata block.
|
||||
|
||||
- `--chmod=`*mode*[`,`*mode*]...|`norm`:
|
||||
Recursively changes the mode bits of all entries in the generated file system.
|
||||
Accepts the same mode specifications as the `chmod` utility and additionally
|
||||
supports the `D` and `F` modifiers to limit a specification to directories
|
||||
or non-directories. As a special case, you can pass `--chmod=norm` to
|
||||
"normalize" the permissions across the file system; this is equivalent to
|
||||
using `--chmod=ug-st,=Xr`.
|
||||
|
||||
- `--order=none`|`path`|`similarity`|`nilsimsa`[`:`*limit*[`:`*depth*[`:`*mindepth*]]]|`script`:
|
||||
The order in which inodes will be written to the file system. Choosing `none`,
|
||||
the inodes will be stored in the order in which they are discovered. With
|
||||
|
35
include/dwarfs/chmod_transformer.h
Normal file
35
include/dwarfs/chmod_transformer.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/**
|
||||
* \author Marcus Holland-Moritz (github@mhxnet.de)
|
||||
* \copyright Copyright (c) Marcus Holland-Moritz
|
||||
*
|
||||
* This file is part of dwarfs.
|
||||
*
|
||||
* dwarfs 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.
|
||||
*
|
||||
* dwarfs 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 dwarfs. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include "dwarfs/entry_transformer.h"
|
||||
|
||||
namespace dwarfs {
|
||||
|
||||
std::unique_ptr<entry_transformer>
|
||||
create_chmod_transformer(std::string_view spec, uint16_t umask);
|
||||
|
||||
} // namespace dwarfs
|
359
src/dwarfs/chmod_transformer.cpp
Normal file
359
src/dwarfs/chmod_transformer.cpp
Normal file
@ -0,0 +1,359 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/**
|
||||
* \author Marcus Holland-Moritz (github@mhxnet.de)
|
||||
* \copyright Copyright (c) Marcus Holland-Moritz
|
||||
*
|
||||
* This file is part of dwarfs.
|
||||
*
|
||||
* dwarfs 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.
|
||||
*
|
||||
* dwarfs 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 dwarfs. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "dwarfs/chmod_transformer.h"
|
||||
#include "dwarfs/entry_interface.h"
|
||||
|
||||
namespace dwarfs {
|
||||
|
||||
namespace {
|
||||
|
||||
uint16_t constexpr all_perm_bits = 07777;
|
||||
uint16_t constexpr all_exec_bits = S_IXUSR | S_IXGRP | S_IXOTH;
|
||||
|
||||
enum class oper { NONE, ADD_BITS, SUB_BITS, SET_BITS, OCT_BITS };
|
||||
|
||||
std::tuple<uint16_t, uint16_t>
|
||||
compute_perm_and_or(oper op, uint16_t hi_bits, uint16_t setid_bits,
|
||||
uint16_t affected, uint16_t perms, uint16_t umask) {
|
||||
uint16_t op_bits = hi_bits;
|
||||
uint16_t perm_and;
|
||||
uint16_t perm_or;
|
||||
|
||||
if (affected) {
|
||||
op_bits |= affected * perms;
|
||||
} else {
|
||||
affected = all_exec_bits;
|
||||
op_bits |= (affected * perms) & ~umask;
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case oper::ADD_BITS:
|
||||
perm_and = all_perm_bits;
|
||||
perm_or = op_bits;
|
||||
break;
|
||||
|
||||
case oper::SUB_BITS:
|
||||
perm_and = all_perm_bits & ~op_bits;
|
||||
perm_or = 0;
|
||||
break;
|
||||
|
||||
case oper::SET_BITS:
|
||||
perm_and = all_perm_bits & ~((affected * S_IRWXO) | setid_bits);
|
||||
perm_or = op_bits;
|
||||
break;
|
||||
|
||||
case oper::OCT_BITS:
|
||||
perm_and = 0;
|
||||
perm_or = op_bits;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error("missing operation in chmod expression");
|
||||
}
|
||||
|
||||
return {perm_and, perm_or};
|
||||
}
|
||||
|
||||
uint16_t modify_perms(uint16_t perm, bool isdir, uint16_t perm_and,
|
||||
uint16_t perm_or, bool flag_X) {
|
||||
auto new_perm = perm;
|
||||
|
||||
new_perm &= perm_and;
|
||||
|
||||
if (!flag_X or (perm & all_exec_bits) != 0 or isdir) {
|
||||
new_perm |= perm_or;
|
||||
} else {
|
||||
new_perm |= perm_or & ~all_exec_bits;
|
||||
}
|
||||
|
||||
return new_perm;
|
||||
}
|
||||
|
||||
class permission_modifier {
|
||||
public:
|
||||
virtual ~permission_modifier() = default;
|
||||
|
||||
virtual uint16_t modify(uint16_t perms, bool isdir) const = 0;
|
||||
};
|
||||
|
||||
class static_permission_modifier : public permission_modifier {
|
||||
public:
|
||||
static_permission_modifier(uint16_t perm_and, uint16_t perm_or, bool flag_X)
|
||||
: perm_and_{perm_and}
|
||||
, perm_or_{perm_or}
|
||||
, flag_X_{flag_X} {}
|
||||
|
||||
uint16_t modify(uint16_t perms, bool isdir) const override {
|
||||
return modify_perms(perms, isdir, perm_and_, perm_or_, flag_X_);
|
||||
}
|
||||
|
||||
private:
|
||||
uint16_t const perm_and_;
|
||||
uint16_t const perm_or_;
|
||||
bool flag_X_;
|
||||
};
|
||||
|
||||
class dynamic_permission_modifier : public permission_modifier {
|
||||
public:
|
||||
dynamic_permission_modifier(oper op, uint16_t setid_bits, uint16_t affected,
|
||||
uint16_t perms_shift, uint16_t umask)
|
||||
: op_{op}
|
||||
, setid_bits_{setid_bits}
|
||||
, affected_{affected}
|
||||
, perms_shift_{perms_shift}
|
||||
, umask_{umask} {}
|
||||
|
||||
uint16_t modify(uint16_t perms, bool isdir) const override {
|
||||
uint16_t dyn_perms = (perms >> perms_shift_) & 07;
|
||||
auto [perm_and, perm_or] =
|
||||
compute_perm_and_or(op_, 0, setid_bits_, affected_, dyn_perms, umask_);
|
||||
return modify_perms(perms, isdir, perm_and, perm_or, false);
|
||||
}
|
||||
|
||||
private:
|
||||
oper const op_;
|
||||
uint16_t const setid_bits_;
|
||||
uint16_t const affected_;
|
||||
uint16_t const perms_shift_;
|
||||
uint16_t const umask_;
|
||||
};
|
||||
|
||||
class chmod_transformer : public entry_transformer {
|
||||
public:
|
||||
chmod_transformer(std::string_view spec, uint16_t umask);
|
||||
|
||||
void transform(entry_interface& ei) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<permission_modifier const> modifier_;
|
||||
bool flag_D_{false};
|
||||
bool flag_F_{false};
|
||||
};
|
||||
|
||||
chmod_transformer::chmod_transformer(std::string_view spec, uint16_t umask) {
|
||||
enum class state { PARSE_WHERE, PARSE_PERMS, PARSE_OCTAL };
|
||||
state st{state::PARSE_WHERE};
|
||||
oper op{oper::NONE};
|
||||
bool flag_X{false};
|
||||
uint16_t setid_bits{0};
|
||||
uint16_t hi_bits{0};
|
||||
uint16_t affected{0};
|
||||
uint16_t perms{0};
|
||||
std::optional<uint16_t> perms_shift;
|
||||
|
||||
for (auto c : spec) {
|
||||
switch (st) {
|
||||
case state::PARSE_WHERE:
|
||||
switch (c) {
|
||||
case 'D':
|
||||
if (flag_F_) {
|
||||
throw std::runtime_error(
|
||||
"cannot combine D and F in chmod expression");
|
||||
}
|
||||
flag_D_ = true;
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
if (flag_D_) {
|
||||
throw std::runtime_error(
|
||||
"cannot combine D and F in chmod expression");
|
||||
}
|
||||
flag_F_ = true;
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
affected |= S_IXUSR;
|
||||
setid_bits |= S_ISUID;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
affected |= S_IXGRP;
|
||||
setid_bits |= S_ISGID;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
affected |= S_IXOTH;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
affected |= all_exec_bits;
|
||||
break;
|
||||
|
||||
case '+':
|
||||
op = oper::ADD_BITS;
|
||||
st = state::PARSE_PERMS;
|
||||
break;
|
||||
|
||||
case '-':
|
||||
op = oper::SUB_BITS;
|
||||
st = state::PARSE_PERMS;
|
||||
break;
|
||||
|
||||
case '=':
|
||||
op = oper::SET_BITS;
|
||||
st = state::PARSE_PERMS;
|
||||
break;
|
||||
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
if (affected) {
|
||||
throw std::runtime_error(
|
||||
"unexpected octal digit in chmod expression");
|
||||
}
|
||||
affected = 1;
|
||||
perms = c - '0';
|
||||
op = oper::OCT_BITS;
|
||||
st = state::PARSE_OCTAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
fmt::format("unexpected character in chmod expression: {}", c));
|
||||
}
|
||||
break;
|
||||
|
||||
case state::PARSE_PERMS:
|
||||
switch (c) {
|
||||
case 'r':
|
||||
perms |= S_IROTH;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
perms |= S_IWOTH;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
flag_X = true;
|
||||
[[fallthrough]];
|
||||
|
||||
case 'x':
|
||||
perms |= S_IXOTH;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
// default to S_ISUID unless explicitly specified
|
||||
hi_bits |= setid_bits ? setid_bits : S_ISUID;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
hi_bits |= S_ISVTX;
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
case 'g':
|
||||
case 'o':
|
||||
if (perms_shift) {
|
||||
throw std::runtime_error(
|
||||
"only one of [ugo] allowed in permission specification");
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 'u':
|
||||
perms_shift = 6;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
perms_shift = 3;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
perms_shift = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
fmt::format("unexpected character in chmod expression: {}", c));
|
||||
}
|
||||
break;
|
||||
|
||||
case state::PARSE_OCTAL:
|
||||
if (c < '0' || c > '7') {
|
||||
throw std::runtime_error(
|
||||
fmt::format("unexpected character in chmod expression: {}", c));
|
||||
}
|
||||
|
||||
perms <<= 3;
|
||||
perms |= c - '0';
|
||||
|
||||
if (perms > all_perm_bits) {
|
||||
throw std::runtime_error("octal chmod expression out of range");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (perms_shift && (perms || hi_bits || flag_X)) {
|
||||
throw std::runtime_error(
|
||||
"[ugo] cannot be combined with other permission specifiers");
|
||||
}
|
||||
|
||||
if (perms_shift) {
|
||||
modifier_ = std::make_unique<dynamic_permission_modifier>(
|
||||
op, setid_bits, affected, *perms_shift, umask);
|
||||
} else {
|
||||
auto [perm_and, perm_or] =
|
||||
compute_perm_and_or(op, hi_bits, setid_bits, affected, perms, umask);
|
||||
|
||||
modifier_ =
|
||||
std::make_unique<static_permission_modifier>(perm_and, perm_or, flag_X);
|
||||
}
|
||||
}
|
||||
|
||||
void chmod_transformer::transform(entry_interface& ei) {
|
||||
// skip entries for which this isn't intended
|
||||
if ((flag_D_ and !ei.is_directory()) or (flag_F_ and ei.is_directory())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ei.set_permissions(
|
||||
modifier_->modify(ei.get_permissions(), ei.is_directory()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<entry_transformer>
|
||||
create_chmod_transformer(std::string_view spec, uint16_t umask) {
|
||||
return std::make_unique<chmod_transformer>(spec, umask);
|
||||
}
|
||||
|
||||
} // namespace dwarfs
|
@ -40,6 +40,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@ -54,6 +55,7 @@
|
||||
#include "dwarfs/block_compressor.h"
|
||||
#include "dwarfs/block_manager.h"
|
||||
#include "dwarfs/builtin_script.h"
|
||||
#include "dwarfs/chmod_transformer.h"
|
||||
#include "dwarfs/console_writer.h"
|
||||
#include "dwarfs/entry.h"
|
||||
#include "dwarfs/error.h"
|
||||
@ -372,7 +374,8 @@ int mkdwarfs(int argc, char** argv) {
|
||||
std::string path, output, memory_limit, script_arg, compression, header,
|
||||
schema_compression, metadata_compression, log_level_str, timestamp,
|
||||
time_resolution, order, progress_mode, recompress_opts, pack_metadata,
|
||||
file_hash_algo, debug_filter, max_similarity_size, input_list_str;
|
||||
file_hash_algo, debug_filter, max_similarity_size, input_list_str,
|
||||
chmod_str;
|
||||
std::vector<std::string> filter;
|
||||
size_t num_workers, num_scanner_workers;
|
||||
bool no_progress = false, remove_header = false, no_section_index = false,
|
||||
@ -475,6 +478,9 @@ int mkdwarfs(int argc, char** argv) {
|
||||
("time-resolution",
|
||||
po::value<std::string>(&time_resolution)->default_value("sec"),
|
||||
resolution_desc.c_str())
|
||||
("chmod",
|
||||
po::value<std::string>(&chmod_str),
|
||||
"recursively apply permission changes")
|
||||
("order",
|
||||
po::value<std::string>(&order),
|
||||
order_desc.c_str())
|
||||
@ -838,7 +844,7 @@ int mkdwarfs(int argc, char** argv) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!filter.empty()) {
|
||||
if (!filter.empty() or vm.count("chmod")) {
|
||||
if (script) {
|
||||
std::cerr
|
||||
<< "error: scripts and filters are not simultaneously supported\n";
|
||||
@ -847,15 +853,32 @@ int mkdwarfs(int argc, char** argv) {
|
||||
|
||||
auto bs = std::make_shared<builtin_script>(lgr);
|
||||
|
||||
bs->set_root_path(path);
|
||||
if (!filter.empty()) {
|
||||
bs->set_root_path(path);
|
||||
|
||||
for (auto const& rule : filter) {
|
||||
try {
|
||||
bs->add_filter_rule(rule);
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "error: could not parse filter rule '" << rule
|
||||
<< "': " << e.what() << "\n";
|
||||
return 1;
|
||||
for (auto const& rule : filter) {
|
||||
try {
|
||||
bs->add_filter_rule(rule);
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "error: could not parse filter rule '" << rule
|
||||
<< "': " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.count("chmod")) {
|
||||
if (chmod_str == "norm") {
|
||||
chmod_str = "ug-st,=Xr";
|
||||
}
|
||||
|
||||
std::vector<std::string_view> chmod_exprs;
|
||||
boost::split(chmod_exprs, chmod_str, boost::is_any_of(","));
|
||||
auto mask = ::umask(0);
|
||||
::umask(mask);
|
||||
|
||||
for (auto expr : chmod_exprs) {
|
||||
bs->add_transformer(create_chmod_transformer(expr, mask));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user