Skip to content

Commit 02fae07

Browse files
authored
Merge pull request #15 from logos-co/fix/preserve-file-permissions
fix: Preserve file permissions when writing tar.
2 parents d9a7413 + 306e5c6 commit 02fae07

File tree

6 files changed

+78
-16
lines changed

6 files changed

+78
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ build/
22
.DS_Store
33
Testing/
44
result
5+
.idea/

src/core/package.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ Package::Result Package::save(const std::filesystem::path& lgxPath) const {
129129
addedDirs.insert(dirPath);
130130
}
131131
} else {
132-
writer.addFile(entry.path, entry.data);
132+
writer.addEntry(entry);
133133
}
134134
}
135135

@@ -452,12 +452,24 @@ Package::Result Package::addFilesystemEntries(
452452
entry.path = normalizedBase;
453453
entry.data = std::move(data);
454454
entry.isDirectory = false;
455+
456+
auto status = fs::status(fsPath, ec);
457+
if (!ec) {
458+
entry.mode = static_cast<uint32_t>(status.permissions() & fs::perms::mask);
459+
}
460+
455461
entries_.push_back(std::move(entry));
456462
} else if (fs::is_directory(fsPath, ec)) {
457463
// Directory - add entry for the directory itself
458464
TarEntry dirEntry;
459465
dirEntry.path = normalizedBase;
460466
dirEntry.isDirectory = true;
467+
468+
auto status = fs::status(fsPath, ec);
469+
if (!ec) {
470+
dirEntry.mode = static_cast<uint32_t>(status.permissions() & fs::perms::mask);
471+
}
472+
461473
entries_.push_back(dirEntry);
462474

463475
// Recursively add contents
@@ -481,6 +493,12 @@ Package::Result Package::addFilesystemEntries(
481493
TarEntry entry;
482494
entry.path = *normalizedPathOpt;
483495
entry.isDirectory = true;
496+
497+
auto status = fs::status(item.path(), ec);
498+
if (!ec) {
499+
entry.mode = static_cast<uint32_t>(status.permissions() & fs::perms::mask);
500+
}
501+
484502
entries_.push_back(std::move(entry));
485503
} else if (fs::is_regular_file(item.path(), ec)) {
486504
std::ifstream file(item.path(), std::ios::binary);
@@ -497,6 +515,12 @@ Package::Result Package::addFilesystemEntries(
497515
entry.path = *normalizedPathOpt;
498516
entry.data = std::move(data);
499517
entry.isDirectory = false;
518+
519+
auto status = fs::status(item.path(), ec);
520+
if (!ec) {
521+
entry.mode = static_cast<uint32_t>(status.permissions() & fs::perms::mask);
522+
}
523+
500524
entries_.push_back(std::move(entry));
501525
} else {
502526
// Skip symlinks, special files, etc.
@@ -577,6 +601,14 @@ Package::Result Package::extractVariant(
577601
if (!file) {
578602
return Result::fail("Failed to write file: " + fullPath.string());
579603
}
604+
file.close();
605+
606+
if (entry.mode != 0) {
607+
fs::permissions(fullPath, static_cast<fs::perms>(entry.mode & 0777), ec);
608+
if (ec) {
609+
return Result::fail("Failed to set permissions on: " + fullPath.string() + " - " + ec.message());
610+
}
611+
}
580612
}
581613
}
582614

src/core/tar_reader.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,7 @@ TarReader::ReadResult TarReader::read(const std::vector<uint8_t>& tarData) {
158158
zeroBlockCount = 0;
159159
const auto& info = *infoOpt;
160160

161-
TarEntry entry;
162-
entry.path = info.path;
163-
entry.isDirectory = info.isDirectory;
161+
TarEntry entry(info.path, info.isDirectory, info.mode);
164162

165163
// Move past header
166164
offset += BLOCK_SIZE;
@@ -324,10 +322,8 @@ bool TarReader::iterate(
324322
zeroBlockCount = 0;
325323
const auto& info = *infoOpt;
326324

327-
TarEntry entry;
328-
entry.path = info.path;
329-
entry.isDirectory = info.isDirectory;
330-
325+
TarEntry entry(info.path, info.isDirectory, info.mode);
326+
331327
// Move past header
332328
offset += BLOCK_SIZE;
333329

src/core/tar_writer.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,15 @@ std::vector<uint8_t> DeterministicTarWriter::createHeader(const TarEntry& entry)
128128

129129
// Name (0-99)
130130
std::memcpy(header.data(), name.c_str(), std::min(name.length(), NAME_SIZE));
131-
131+
132132
// Mode (100-107)
133-
writeOctal(header.data() + 100, 8, entry.isDirectory ? DIR_MODE : FILE_MODE);
133+
uint32_t mode = entry.mode & 0777;
134+
if (entry.isDirectory) {
135+
mode = DIR_MODE;
136+
} else if (mode == 0) {
137+
mode = FILE_MODE;
138+
}
139+
writeOctal(header.data() + 100, 8, mode);
134140

135141
// UID (108-115)
136142
writeOctal(header.data() + 108, 8, UID);

src/core/tar_writer.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ struct TarEntry {
1616
std::string path; // NFC-normalized archive path
1717
std::vector<uint8_t> data; // File contents (empty for directories)
1818
bool isDirectory;
19+
uint32_t mode; // File mode (permissions)
1920

20-
TarEntry() : isDirectory(false) {}
21-
TarEntry(const std::string& p, bool isDir = false)
22-
: path(p), isDirectory(isDir) {}
23-
TarEntry(const std::string& p, const std::vector<uint8_t>& d)
24-
: path(p), data(d), isDirectory(false) {}
21+
TarEntry() : isDirectory(false), mode(0) {}
22+
TarEntry(const std::string& p, bool isDir = false, uint32_t m = 0)
23+
: path(p), isDirectory(isDir), mode(m) {}
24+
TarEntry(const std::string& p, const std::vector<uint8_t>& d, uint32_t m = 0)
25+
: path(p), data(d), isDirectory(false), mode(m) {}
26+
TarEntry(const std::string& p, const std::string& d, uint32_t m = 0)
27+
: path(p), data(d.begin(), d.end()), isDirectory(false), mode(m) {}
2528
};
2629

2730
/**
@@ -30,7 +33,7 @@ struct TarEntry {
3033
* Determinism is achieved by:
3134
* - Lexicographic sorting of entries by NFC-normalized path bytes
3235
* - Fixed metadata: uid=0, gid=0, uname="", gname="", mtime=0
33-
* - Fixed modes: dirs=0755, files=0644
36+
* - Modes: dirs=0755, files preserve their mode or 0644 if not set
3437
* - USTAR format for consistency
3538
*/
3639
class DeterministicTarWriter {

tests/test_tar_writer.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,30 @@ TEST(TarWriterTest, FixedMetadata) {
150150
}
151151
}
152152

153+
TEST(TarWriterTest, CustomMode) {
154+
DeterministicTarWriter writer;
155+
156+
// Add file with custom mode
157+
TarEntry entry1("script.sh", std::string("echo hello"), 0700);
158+
writer.addEntry(entry1);
159+
160+
// Add directory with custom mode
161+
TarEntry entry2("tools", true, 0777);
162+
writer.addEntry(entry2);
163+
164+
auto tarData = writer.finalize();
165+
auto result = TarReader::read(tarData);
166+
ASSERT_TRUE(result.success);
167+
ASSERT_EQ(result.entries.size(), 2);
168+
169+
// Entries are sorted lexicographically: script.sh, tools
170+
EXPECT_EQ(result.entries[0].path, "script.sh");
171+
EXPECT_EQ(result.entries[0].mode, 0700);
172+
173+
EXPECT_EQ(result.entries[1].path, "tools/");
174+
EXPECT_EQ(result.entries[1].mode, 0755); // Directories now always DIR_MODE (0755)
175+
}
176+
153177
// =============================================================================
154178
// Roundtrip Tests
155179
// =============================================================================

0 commit comments

Comments
 (0)