Merge remote-tracking branch 'origin/feat/executability' into dev
This commit is contained in:
+1
-1
@@ -120,7 +120,7 @@ This document maps rsync's full feature set to FastSync's current implementation
|
||||
| `-o`, `--owner` | Preserve owner | ✅ Implemented | Part of -M |
|
||||
| `-g`, `--group` | Preserve group | ✅ Implemented | Part of -M |
|
||||
| `-t`, `--times` | Preserve modification times | ✅ Implemented | Part of -M |
|
||||
| `-E`, `--executability` | Preserve executability | ❌ Not Implemented | |
|
||||
| `-E`, `--executability` | Preserve executability | ✅ Implemented | Preserves executable permission bits (implies metadata preservation) |
|
||||
| `--chmod=CHMOD` | Affect file permissions | ❌ Not Implemented | |
|
||||
| `-A`, `--acls` | Preserve ACLs | ❌ Not Implemented | Removed because it had no effect |
|
||||
| `-X`, `--xattrs` | Preserve extended attributes | ❌ Not Implemented | Removed because it had no effect |
|
||||
|
||||
@@ -388,6 +388,10 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
|
||||
} else if (opt_is(argv[i], "-M", "--preserve")) {
|
||||
config->use_metadata = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled metadata preservation");
|
||||
} else if (opt_is(argv[i], "-E", "--executability")) {
|
||||
config->use_metadata = true;
|
||||
config->use_executability = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled executable permission preservation");
|
||||
} else if (opt_is(argv[i], "-f", "--sendfile")) {
|
||||
config->use_sendfile = true;
|
||||
log_message(LOG_LEVEL_INFO, "Enabled sendfile");
|
||||
@@ -623,7 +627,6 @@ int main(int argc, char* argv[]) {
|
||||
log_message(LOG_LEVEL_INFO, "Enabling metadata preservation for --delta");
|
||||
config->use_metadata = true;
|
||||
}
|
||||
|
||||
/* Initialize TLS if needed */
|
||||
if (config->use_tls)
|
||||
tls_global_init();
|
||||
|
||||
@@ -50,6 +50,7 @@ void print_usage(void) {
|
||||
printf(" -q, --quiet Suppress non-error output\n");
|
||||
printf(" --debug=FLAGS Fine-grained debug logging (use --debug=help for flags)\n");
|
||||
printf(" -M, --preserve Preserve file metadata\n");
|
||||
printf(" -E, --executability Preserve executable permission bits\n");
|
||||
printf(" --chunk-size <n> Chunk size in bytes (default: %d)\n", DEFAULT_CHUNK_SIZE);
|
||||
printf(" --source-dir <path> Source directory\n");
|
||||
printf(" --dest-dir <path> Destination directory\n");
|
||||
|
||||
+9
-5
@@ -17,6 +17,7 @@ static void config_set_defaults(Config* config) {
|
||||
config->use_chunk_serialization = false;
|
||||
config->use_compression = false;
|
||||
config->use_metadata = false;
|
||||
config->use_executability = false;
|
||||
config->show_progress = false;
|
||||
config->dry_run = false;
|
||||
config->remove_source_files = false;
|
||||
@@ -125,9 +126,10 @@ static bool validate_received_config(const Config* config) {
|
||||
return valid_wire_bool(config->save_to_disk) && valid_wire_bool(config->use_multithreading) &&
|
||||
valid_wire_bool(config->use_chunk_serialization) &&
|
||||
valid_wire_bool(config->use_compression) && valid_wire_bool(config->use_metadata) &&
|
||||
valid_wire_bool(config->use_sendfile) && valid_wire_bool(config->use_delete) &&
|
||||
valid_wire_bool(config->use_incremental) && valid_wire_bool(config->size_only) &&
|
||||
valid_wire_bool(config->ignore_times) && valid_wire_bool(config->use_delta) &&
|
||||
valid_wire_bool(config->use_executability) && valid_wire_bool(config->use_sendfile) &&
|
||||
valid_wire_bool(config->use_delete) && valid_wire_bool(config->use_incremental) &&
|
||||
valid_wire_bool(config->size_only) && valid_wire_bool(config->ignore_times) &&
|
||||
valid_wire_bool(config->use_delta) && valid_wire_bool(config->backup) &&
|
||||
valid_wire_bool(config->follow_symlinks) && valid_wire_bool(config->copy_links) &&
|
||||
valid_wire_bool(config->safe_links) && valid_wire_bool(config->copy_unsafe_links) &&
|
||||
valid_wire_bool(config->preserve_hard_links) && valid_wire_bool(config->preserve_acls) &&
|
||||
@@ -237,7 +239,8 @@ static bool send_core_fields(int fd, const Config* c) {
|
||||
return send_str(fd, c->send_directory) && send_str(fd, c->receive_root_directory) &&
|
||||
send_int(fd, c->save_to_disk) && send_int(fd, c->use_multithreading) &&
|
||||
send_int(fd, c->use_chunk_serialization) && send_int(fd, c->use_compression) &&
|
||||
send_int(fd, c->use_metadata) && send_int(fd, c->compression_level) &&
|
||||
send_int(fd, c->use_metadata) && send_int(fd, c->use_executability) &&
|
||||
send_int(fd, c->compression_level) &&
|
||||
send_n_data(fd, &c->chunk_size, sizeof(c->chunk_size)) && send_int(fd, c->use_sendfile);
|
||||
}
|
||||
|
||||
@@ -286,7 +289,8 @@ static bool receive_core_fields(int fd, Config* c) {
|
||||
return false;
|
||||
if (!receive_wire_bool(fd, &c->save_to_disk) || !receive_wire_bool(fd, &c->use_multithreading) ||
|
||||
!receive_wire_bool(fd, &c->use_chunk_serialization) ||
|
||||
!receive_wire_bool(fd, &c->use_compression) || !receive_wire_bool(fd, &c->use_metadata))
|
||||
!receive_wire_bool(fd, &c->use_compression) || !receive_wire_bool(fd, &c->use_metadata) ||
|
||||
!receive_wire_bool(fd, &c->use_executability))
|
||||
return false;
|
||||
if (!receive_int(fd, &value))
|
||||
return false;
|
||||
|
||||
@@ -18,6 +18,7 @@ typedef struct Config {
|
||||
bool use_compression;
|
||||
bool use_sendfile;
|
||||
bool use_metadata;
|
||||
bool use_executability;
|
||||
bool show_progress;
|
||||
bool dry_run;
|
||||
bool remove_source_files;
|
||||
|
||||
+20
-14
@@ -327,8 +327,8 @@ bool file_rename_secure(const char* old_path, const char* new_path) {
|
||||
|
||||
static bool file_to_disk_secure_impl(const char* path, const void* data,
|
||||
unsigned long long data_size, bool inplace, bool sparse,
|
||||
const FileMetadata* metadata, bool update, bool no_replace,
|
||||
bool use_fsync) {
|
||||
const FileMetadata* metadata, bool preserve_executability,
|
||||
bool update, bool no_replace, bool use_fsync) {
|
||||
char* leaf = NULL;
|
||||
int dirfd = file_open_secure_parent(path, &leaf, true);
|
||||
if (dirfd < 0)
|
||||
@@ -350,7 +350,7 @@ static bool file_to_disk_secure_impl(const char* path, const void* data,
|
||||
if (!sparse || data_size == 0 || ftruncate(fd, (off_t)data_size) == 0)
|
||||
ok = write_all(fd, data, data_size);
|
||||
if (ok && metadata)
|
||||
ok = file_restore_metadata_fd(fd, metadata);
|
||||
ok = file_restore_metadata_fd(fd, metadata, preserve_executability);
|
||||
if (ok && use_fsync)
|
||||
ok = fsync(fd) == 0;
|
||||
}
|
||||
@@ -378,7 +378,7 @@ static bool file_to_disk_secure_impl(const char* path, const void* data,
|
||||
if (ok || (!sparse || data_size == 0))
|
||||
ok = write_all(fd, data, data_size);
|
||||
if (ok && metadata)
|
||||
ok = file_restore_metadata_fd(fd, metadata);
|
||||
ok = file_restore_metadata_fd(fd, metadata, preserve_executability);
|
||||
if (ok && use_fsync)
|
||||
ok = fsync(fd) == 0;
|
||||
if (close(fd) != 0)
|
||||
@@ -410,31 +410,37 @@ static bool file_to_disk_secure_impl(const char* path, const void* data,
|
||||
}
|
||||
|
||||
bool file_to_disk_secure(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse, const FileMetadata* metadata) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, false, false, false);
|
||||
bool inplace, bool sparse, const FileMetadata* metadata,
|
||||
bool preserve_executability) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata,
|
||||
preserve_executability, false, false, false);
|
||||
}
|
||||
|
||||
bool file_to_disk_secure_update(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse, const FileMetadata* metadata) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, true, false, false);
|
||||
bool inplace, bool sparse, const FileMetadata* metadata,
|
||||
bool preserve_executability) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata,
|
||||
preserve_executability, true, false, false);
|
||||
}
|
||||
|
||||
bool file_to_disk_secure_with_fsync(const char* path, const void* data,
|
||||
unsigned long long data_size, bool inplace, bool sparse,
|
||||
const FileMetadata* metadata, bool use_fsync) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata, false, false,
|
||||
use_fsync);
|
||||
const FileMetadata* metadata, bool preserve_executability,
|
||||
bool use_fsync) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, inplace, sparse, metadata,
|
||||
preserve_executability, false, false, use_fsync);
|
||||
}
|
||||
|
||||
bool file_to_disk_secure_no_replace(const char* path, const void* data,
|
||||
unsigned long long data_size, bool sparse,
|
||||
const FileMetadata* metadata) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, false, sparse, metadata, false, true, false);
|
||||
const FileMetadata* metadata, bool preserve_executability) {
|
||||
return file_to_disk_secure_impl(path, data, data_size, false, sparse, metadata,
|
||||
preserve_executability, false, true, false);
|
||||
}
|
||||
|
||||
bool file_write_to_disk(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse) {
|
||||
if (!path || (!data && data_size != 0) || has_path_traversal(path))
|
||||
return false;
|
||||
return file_to_disk_secure(path, data, data_size, inplace, sparse, NULL);
|
||||
return file_to_disk_secure(path, data, data_size, inplace, sparse, NULL, false);
|
||||
}
|
||||
|
||||
+7
-4
@@ -32,17 +32,20 @@ int file_open_secure_parent(const char* path, char** leaf_out, bool create_dirs)
|
||||
bool file_ensure_directory_secure(const char* path);
|
||||
bool file_rename_secure(const char* old_path, const char* new_path);
|
||||
bool file_to_disk_secure(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse, const FileMetadata* metadata);
|
||||
bool inplace, bool sparse, const FileMetadata* metadata,
|
||||
bool preserve_executability);
|
||||
bool file_to_disk_secure_with_fsync(const char* path, const void* data,
|
||||
unsigned long long data_size, bool inplace, bool sparse,
|
||||
const FileMetadata* metadata, bool use_fsync);
|
||||
const FileMetadata* metadata, bool preserve_executability,
|
||||
bool use_fsync);
|
||||
/* With update enabled, an existing newer destination is left untouched. The
|
||||
check is descriptor-based for inplace writes; atomic replacement still has
|
||||
an unavoidable final rename race without filesystem locking. */
|
||||
bool file_to_disk_secure_update(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse, const FileMetadata* metadata);
|
||||
bool inplace, bool sparse, const FileMetadata* metadata,
|
||||
bool preserve_executability);
|
||||
bool file_to_disk_secure_no_replace(const char* path, const void* data,
|
||||
unsigned long long data_size, bool sparse,
|
||||
const FileMetadata* metadata);
|
||||
const FileMetadata* metadata, bool preserve_executability);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,6 +27,7 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
||||
bool backup_enabled = config && config->backup && !config->ignore_existing;
|
||||
bool inplace = config && config->inplace;
|
||||
bool sparse = config && config->preserve_sparse;
|
||||
bool preserve_executability = config && config->use_executability;
|
||||
const char* backup_suffix = (config && config->suffix) ? config->suffix : "~";
|
||||
const char* backup_dir = (config && config->backup_dir) ? config->backup_dir : NULL;
|
||||
const char* partial_dir = (config && config->partial_dir) ? config->partial_dir : NULL;
|
||||
@@ -132,13 +133,15 @@ bool file_save_to_disk(const char* root_directory, const File* file, const Confi
|
||||
|
||||
bool ok = config && config->ignore_existing
|
||||
? file_to_disk_secure_no_replace(disk_path, file->data->data, file->data->size,
|
||||
sparse, file->metadata)
|
||||
sparse, file->metadata, preserve_executability)
|
||||
: config && config->update
|
||||
? file_to_disk_secure_update(disk_path, file->data->data, file->data->size,
|
||||
inplace, sparse, file->metadata)
|
||||
inplace, sparse, file->metadata,
|
||||
preserve_executability)
|
||||
: file_to_disk_secure_with_fsync(disk_path, file->data->data, file->data->size,
|
||||
inplace, sparse, file->metadata,
|
||||
config && config->use_fsync);
|
||||
inplace, sparse, file->metadata,
|
||||
preserve_executability,
|
||||
config && config->use_fsync);
|
||||
free(parent_copy);
|
||||
free(backup_path);
|
||||
free(confined_backup);
|
||||
|
||||
@@ -140,7 +140,8 @@ static bool write_all(int fd, const void* data, unsigned long long size) {
|
||||
}
|
||||
|
||||
bool file_store_write_secure(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse, const FileMetadata* metadata) {
|
||||
bool inplace, bool sparse, const FileMetadata* metadata,
|
||||
bool preserve_executability) {
|
||||
char* leaf = NULL;
|
||||
int dirfd = file_store_open_secure_parent(path, &leaf);
|
||||
if (dirfd < 0)
|
||||
@@ -153,7 +154,7 @@ bool file_store_write_secure(const char* path, const void* data, unsigned long l
|
||||
if (!sparse || data_size == 0 || ftruncate(fd, (off_t)data_size) == 0)
|
||||
ok = write_all(fd, data, data_size);
|
||||
if (ok && metadata)
|
||||
ok = file_restore_metadata_fd(fd, metadata);
|
||||
ok = file_restore_metadata_fd(fd, metadata, preserve_executability);
|
||||
}
|
||||
} else {
|
||||
int tmp_size = snprintf(NULL, 0, ".%s.tmp.%ld.%u", leaf, (long)getpid(), 99U);
|
||||
@@ -178,7 +179,7 @@ bool file_store_write_secure(const char* path, const void* data, unsigned long l
|
||||
if (ok || (!sparse || data_size == 0))
|
||||
ok = write_all(fd, data, data_size);
|
||||
if (ok && metadata)
|
||||
ok = file_restore_metadata_fd(fd, metadata);
|
||||
ok = file_restore_metadata_fd(fd, metadata, preserve_executability);
|
||||
if (close(fd) != 0)
|
||||
ok = false;
|
||||
fd = -1;
|
||||
|
||||
@@ -8,6 +8,7 @@ bool file_store_set_authorized_root(int fd, const char* canonical_path);
|
||||
int file_store_open_secure_parent(const char* path, char** leaf_out);
|
||||
bool file_store_rename_secure(const char* old_path, const char* new_path);
|
||||
bool file_store_write_secure(const char* path, const void* data, unsigned long long data_size,
|
||||
bool inplace, bool sparse, const FileMetadata* metadata);
|
||||
bool inplace, bool sparse, const FileMetadata* metadata,
|
||||
bool preserve_executability);
|
||||
|
||||
#endif
|
||||
|
||||
+18
-4
@@ -201,10 +201,21 @@ FileMetadata* metadata_receive(int file_descriptor, int* ok) {
|
||||
return m;
|
||||
}
|
||||
|
||||
void file_restore_metadata(const char* path, const FileMetadata* metadata) {
|
||||
static mode_t metadata_mode(const FileMetadata* metadata, mode_t current_mode,
|
||||
bool preserve_executability) {
|
||||
const mode_t execute_bits = S_IXUSR | S_IXGRP | S_IXOTH;
|
||||
if (preserve_executability)
|
||||
return (current_mode & 0777 & ~execute_bits) | (metadata->mode & execute_bits);
|
||||
return metadata->mode & 0777 & ~(S_IWGRP | S_IWOTH);
|
||||
}
|
||||
|
||||
void file_restore_metadata(const char* path, const FileMetadata* metadata,
|
||||
bool preserve_executability) {
|
||||
if (metadata == NULL)
|
||||
return;
|
||||
mode_t safe_mode = metadata->mode & 0777 & ~(S_IWGRP | S_IWOTH);
|
||||
struct stat current;
|
||||
mode_t current_mode = stat(path, ¤t) == 0 ? current.st_mode : 0;
|
||||
mode_t safe_mode = metadata_mode(metadata, current_mode, preserve_executability);
|
||||
if (chmod(path, safe_mode) != 0) {
|
||||
char* escaped_path = output_escape(path, log_get_8_bit_output());
|
||||
log_message(LOG_LEVEL_WARNING, "Failed to chmod %s: %s",
|
||||
@@ -226,11 +237,14 @@ void file_restore_metadata(const char* path, const FileMetadata* metadata) {
|
||||
}
|
||||
}
|
||||
|
||||
bool file_restore_metadata_fd(int fd, const FileMetadata* metadata) {
|
||||
bool file_restore_metadata_fd(int fd, const FileMetadata* metadata, bool preserve_executability) {
|
||||
if (fd < 0 || metadata == NULL)
|
||||
return metadata == NULL;
|
||||
bool ok = true;
|
||||
mode_t safe_mode = metadata->mode & 0777 & ~(S_IWGRP | S_IWOTH);
|
||||
struct stat current;
|
||||
if (fstat(fd, ¤t) != 0)
|
||||
return false;
|
||||
mode_t safe_mode = metadata_mode(metadata, current.st_mode, preserve_executability);
|
||||
if (fchmod(fd, safe_mode) != 0)
|
||||
ok = false;
|
||||
/* Client uid/gid values are deliberately not authoritative. */
|
||||
|
||||
@@ -30,8 +30,9 @@ void metadata_to_buf(char** buf, const FileMetadata* m);
|
||||
FileMetadata* metadata_from_buf(char** buf);
|
||||
bool metadata_send(int file_descriptor, const FileMetadata* m);
|
||||
FileMetadata* metadata_receive(int file_descriptor, int* ok);
|
||||
void file_restore_metadata(const char* path, const FileMetadata* metadata);
|
||||
bool file_restore_metadata_fd(int fd, const FileMetadata* metadata);
|
||||
void file_restore_metadata(const char* path, const FileMetadata* metadata,
|
||||
bool preserve_executability);
|
||||
bool file_restore_metadata_fd(int fd, const FileMetadata* metadata, bool preserve_executability);
|
||||
|
||||
/* Compare timestamps using rsync's whole-second modification window. */
|
||||
bool metadata_mtime_matches(time_t left_sec, long left_nsec, time_t right_sec, long right_nsec,
|
||||
|
||||
@@ -170,6 +170,26 @@ class TestArchiveMode:
|
||||
assert not mismatches, f"Mismatch: {mismatches}"
|
||||
|
||||
|
||||
class TestExecutability:
|
||||
def test_preserves_only_executable_bits(self, shared_server):
|
||||
source = os.path.join(TEST_DATA_DIR, "executability_source")
|
||||
dest = os.path.join(TEST_DATA_DIR, "executability_dest")
|
||||
clean_dir(source)
|
||||
clean_dir(dest)
|
||||
source_file = os.path.join(source, "tool.sh")
|
||||
with open(source_file, "w") as f:
|
||||
f.write("#!/bin/sh\necho test\n")
|
||||
os.chmod(source_file, 0o751)
|
||||
|
||||
result, _ = run_client(source, dest, flags=["-E"], port=shared_server.port)
|
||||
assert result.returncode == 0, f"Executability sync failed: {result.stderr[:200]}"
|
||||
received_file = os.path.join(get_dest_received_dir(dest, source), "tool.sh")
|
||||
received_mode = os.stat(received_file).st_mode
|
||||
assert received_mode & 0o111 == 0o111
|
||||
assert received_mode & 0o600 == 0o600
|
||||
assert received_mode & 0o077 == 0o011
|
||||
|
||||
|
||||
class TestExclude:
|
||||
def test_exclude_single(self, shared_server):
|
||||
clean_dir(DEST_DIR)
|
||||
|
||||
@@ -237,6 +237,19 @@ static void test_parse_args_ignore_existing() {
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
static void test_parse_args_executability() {
|
||||
Config* cfg = config_create();
|
||||
char* argv[] = {"fastsync", "-E", "/src", "/dst"};
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), 0);
|
||||
EXPECT_TRUE(cfg->use_executability);
|
||||
EXPECT_TRUE(cfg->use_metadata);
|
||||
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* Test parse_args rejects port > 65535 */
|
||||
static void test_parse_args_invalid_port() {
|
||||
Config* cfg = config_create();
|
||||
@@ -657,6 +670,7 @@ void test_client_cli() {
|
||||
test_parse_args_valid_port();
|
||||
test_parse_args_size_only();
|
||||
test_parse_args_ignore_existing();
|
||||
test_parse_args_executability();
|
||||
test_parse_args_invalid_port();
|
||||
test_parse_args_non_numeric_port();
|
||||
test_parse_args_invalid_server_port();
|
||||
|
||||
@@ -124,6 +124,7 @@ static void test_config_send_receive() {
|
||||
send_cfg->use_chunk_serialization = true;
|
||||
send_cfg->use_compression = true;
|
||||
send_cfg->use_metadata = true;
|
||||
send_cfg->use_executability = true;
|
||||
send_cfg->use_delta = true;
|
||||
send_cfg->whole_file = true;
|
||||
send_cfg->ignore_times = true;
|
||||
@@ -168,6 +169,8 @@ static void test_config_send_receive() {
|
||||
ok = false;
|
||||
if (recv_cfg->chunk_size != 1024)
|
||||
ok = false;
|
||||
if (!recv_cfg->use_executability)
|
||||
ok = false;
|
||||
if (!recv_cfg->size_only)
|
||||
ok = false;
|
||||
if (!recv_cfg->ignore_times)
|
||||
|
||||
+1
-1
@@ -274,7 +274,7 @@ static void test_file_write_to_disk_with_fsync() {
|
||||
const char* path = "test_file_write_to_disk_fsync.txt";
|
||||
const char* content = "fsync file content";
|
||||
EXPECT_TRUE(
|
||||
file_to_disk_secure_with_fsync(path, content, strlen(content), false, false, NULL, true));
|
||||
file_to_disk_secure_with_fsync(path, content, strlen(content), false, false, NULL, false, true));
|
||||
struct stat st;
|
||||
EXPECT_EQ_INT(stat(path, &st), 0);
|
||||
EXPECT_EQ_INT((int)st.st_size, (int)strlen(content));
|
||||
|
||||
+32
-1
@@ -146,7 +146,7 @@ static void test_file_restore_metadata() {
|
||||
m.mtime_sec = 1234567890;
|
||||
m.mtime_nsec = 0;
|
||||
|
||||
file_restore_metadata(path, &m);
|
||||
file_restore_metadata(path, &m, false);
|
||||
|
||||
struct stat st;
|
||||
EXPECT_EQ_INT(stat(path, &st), 0);
|
||||
@@ -156,6 +156,35 @@ static void test_file_restore_metadata() {
|
||||
unlink(path);
|
||||
}
|
||||
|
||||
static void test_file_restore_executability_only() {
|
||||
const char* path = "temp_exec_restore_test.txt";
|
||||
EXPECT_TRUE(file_write_to_disk(path, "x", 1, false, false));
|
||||
EXPECT_EQ_INT(chmod(path, 0644), 0);
|
||||
|
||||
FileMetadata m = {
|
||||
.mode = 0751, .uid = getuid(), .gid = getgid(), .mtime_sec = 0, .mtime_nsec = 0};
|
||||
file_restore_metadata(path, &m, true);
|
||||
|
||||
struct stat st;
|
||||
EXPECT_EQ_INT(stat(path, &st), 0);
|
||||
EXPECT_EQ_INT(st.st_mode & 0777, 0755);
|
||||
unlink(path);
|
||||
}
|
||||
|
||||
static void test_directory_restore_executability_only() {
|
||||
const char* path = "temp_exec_restore_test_dir";
|
||||
EXPECT_EQ_INT(mkdir(path, 0700), 0);
|
||||
|
||||
FileMetadata m = {
|
||||
.mode = 0755, .uid = getuid(), .gid = getgid(), .mtime_sec = 0, .mtime_nsec = 0};
|
||||
file_restore_metadata(path, &m, true);
|
||||
|
||||
struct stat st;
|
||||
EXPECT_EQ_INT(stat(path, &st), 0);
|
||||
EXPECT_EQ_INT(st.st_mode & 0777, 0711);
|
||||
rmdir(path);
|
||||
}
|
||||
|
||||
void test_metadata() {
|
||||
test_metadata_to_from_buf_roundtrip();
|
||||
test_metadata_to_buf_null();
|
||||
@@ -165,4 +194,6 @@ void test_metadata() {
|
||||
test_metadata_rejects_invalid_values();
|
||||
test_metadata_mtime_window();
|
||||
test_file_restore_metadata();
|
||||
test_file_restore_executability_only();
|
||||
test_directory_restore_executability_only();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user