Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,75 @@ jobs:
cd ..
rm -rf extract_test

# Test symlink functionality
echo "Testing symlink functionality..."

# Create test data with symlinks
mkdir -p test_symlinks
cd test_symlinks
echo "Target file content" > target.txt
mkdir subdir
echo "Nested target" > subdir/nested.txt

# Create various types of symlinks
ln -sf target.txt simple_link.txt
ln -sf subdir/nested.txt relative_link.txt
ln -sf /tmp/absolute_target absolute_link.txt
ln -sf nonexistent_file broken_link.txt
ln -sf subdir dir_link
cd ..

# Test container creation with symlinks
./build/bin/bfc create test_symlinks.bfc test_symlinks/
echo "Container with symlinks created"

# Test symlink listing - verify 'l' prefix appears
./build/bin/bfc list -l test_symlinks.bfc | grep "lrwxr-xr-x.*simple_link.txt" && echo "Simple symlink listed correctly" || echo "Simple symlink listing failed"
./build/bin/bfc list -l test_symlinks.bfc | grep "lrwxr-xr-x.*absolute_link.txt" && echo "Absolute symlink listed correctly" || echo "Absolute symlink listing failed"

# Test symlink counting in container info
./build/bin/bfc info test_symlinks.bfc | grep "Symlinks:" && echo "Symlinks counted in container info" || echo "Symlink counting failed"

# Test symlink-specific info
./build/bin/bfc info test_symlinks.bfc simple_link.txt | grep "Type: Symlink" && echo "Symlink type displayed correctly" || echo "Symlink type display failed"

# Test symlink extraction
mkdir -p extract_symlinks
cd extract_symlinks
../build/bin/bfc extract ../test_symlinks.bfc

# Verify symlinks were extracted correctly
[ -L simple_link.txt ] && echo "Simple symlink extracted as symlink" || echo "Simple symlink not extracted as symlink"
[ -L broken_link.txt ] && echo "Broken symlink extracted as symlink" || echo "Broken symlink not extracted as symlink"

# Verify symlink targets
if [ -L simple_link.txt ]; then
TARGET=$(readlink simple_link.txt)
[ "$TARGET" = "target.txt" ] && echo "Simple symlink target correct" || echo "Simple symlink target incorrect: $TARGET"
fi

if [ -L absolute_link.txt ]; then
TARGET=$(readlink absolute_link.txt)
[ "$TARGET" = "/tmp/absolute_target" ] && echo "Absolute symlink target correct" || echo "Absolute symlink target incorrect: $TARGET"
fi

cd ..
rm -rf extract_symlinks

# Test symlinks with compression and encryption
./build/bin/bfc create -c zstd -e testpass symlinks_comp_enc.bfc test_symlinks/simple_link.txt test_symlinks/absolute_link.txt
echo "Symlinks work with compression and encryption"

mkdir -p extract_comp_enc
cd extract_comp_enc
../build/bin/bfc extract -p testpass ../symlinks_comp_enc.bfc
[ -L simple_link.txt ] && echo "Compressed+encrypted symlink extracted correctly" || echo "Compressed+encrypted symlink extraction failed"
cd ..
rm -rf extract_comp_enc

# Clean up symlink test files
rm -rf test_symlinks test_symlinks.bfc symlinks_comp_enc.bfc

# Test encryption functionality
echo "Testing encryption features..."
./build/bin/bfc create -e testpassword123 test_encrypted.bfc test_data/
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ configure:
@echo "Configuring CMake build..."
cmake -B $(BUILD_DIR) \
-DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \
-DBFC_WITH_ZSTD=ON
-DBFC_BUILD_TESTS=ON\
-DBFC_WITH_ZSTD=ON \
-DBFC_WITH_SODIUM=ON

# Build the project
Expand Down
2 changes: 2 additions & 0 deletions include/bfc.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ int bfc_create(const char* filename, uint32_t block_size, uint64_t features, bfc
int bfc_add_file(bfc_t* w, const char* container_path, FILE* src, uint32_t mode, uint64_t mtime_ns,
uint32_t* out_crc);
int bfc_add_dir(bfc_t* w, const char* container_dir, uint32_t mode, uint64_t mtime_ns);
int bfc_add_symlink(bfc_t* w, const char* container_path, const char* link_target, uint32_t mode,
uint64_t mtime_ns);

/* --- Compression Configuration --- */
int bfc_set_compression(bfc_t* w, uint8_t comp_type, int level);
Expand Down
52 changes: 48 additions & 4 deletions src/cli/cmd_create.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

#define _GNU_SOURCE
#include "cli.h"
#include <dirent.h>
#include <errno.h>
Expand Down Expand Up @@ -232,6 +233,42 @@ static int add_file_to_container(bfc_t* writer, const char* file_path, const cha
return 0;
}

static int add_symlink_to_container(bfc_t* writer, const char* link_path,
const char* container_path) {
print_verbose("Adding symlink: %s -> %s", link_path, container_path);

// Read the symlink target
char target[1024];
ssize_t target_len = readlink(link_path, target, sizeof(target) - 1);
if (target_len == -1) {
print_error("Cannot readlink '%s': %s", link_path, strerror(errno));
return -1;
}
target[target_len] = '\0';

// Get symlink stats
struct stat st;
if (lstat(link_path, &st) != 0) {
print_error("Cannot lstat symlink '%s': %s", link_path, strerror(errno));
return -1;
}

// Add symlink to container
uint64_t mtime_ns = (uint64_t) st.st_mtime * 1000000000ULL;
int result = bfc_add_symlink(writer, container_path, target, st.st_mode & 0777, mtime_ns);

if (result != BFC_OK) {
print_error("Failed to add symlink '%s': %s", container_path, bfc_error_string(result));
return -1;
}

if (!g_options.quiet) {
printf("Added: %s -> %s\n", container_path, target);
}

return 0;
}

static int add_directory_to_container(bfc_t* writer, const char* dir_path,
const char* container_path);

Expand All @@ -249,15 +286,17 @@ static int process_directory_entry(bfc_t* writer, const char* base_path, const c
}

struct stat st;
if (stat(full_path, &st) != 0) {
print_error("Cannot stat '%s': %s", full_path, strerror(errno));
if (lstat(full_path, &st) != 0) {
print_error("Cannot lstat '%s': %s", full_path, strerror(errno));
return -1;
}

if (S_ISREG(st.st_mode)) {
return add_file_to_container(writer, full_path, container_path);
} else if (S_ISDIR(st.st_mode)) {
return add_directory_to_container(writer, full_path, container_path);
} else if (S_ISLNK(st.st_mode)) {
return add_symlink_to_container(writer, full_path, container_path);
} else {
print_verbose("Skipping special file: %s", full_path);
return 0;
Expand Down Expand Up @@ -417,7 +456,7 @@ int cmd_create(int argc, char* argv[]) {
const char* input_path = opts.input_paths[i];

struct stat st;
if (stat(input_path, &st) != 0) {
if (lstat(input_path, &st) != 0) {
print_error("Cannot access '%s': %s", input_path, strerror(errno));
bfc_close(writer);
return 1;
Expand Down Expand Up @@ -448,8 +487,13 @@ int cmd_create(int argc, char* argv[]) {
bfc_close(writer);
return 1;
}
} else if (S_ISLNK(st.st_mode)) {
if (add_symlink_to_container(writer, input_path, basename) != 0) {
bfc_close(writer);
return 1;
}
} else {
print_error("'%s' is not a regular file or directory", input_path);
print_error("'%s' is not a regular file, directory, or symlink", input_path);
bfc_close(writer);
return 1;
}
Expand Down
64 changes: 64 additions & 0 deletions src/cli/cmd_extract.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

Expand Down Expand Up @@ -319,6 +320,67 @@
return 0;
}

static int extract_symlink(bfc_t* reader, const bfc_entry_t* entry, const char* output_path,
int force) {
// Check if file exists
struct stat st;
if (lstat(output_path, &st) == 0) {
if (!S_ISLNK(st.st_mode)) {
if (!force) {
print_error("'%s' exists but is not a symlink. Use -f to overwrite.", output_path);
return -1;
}
}
// Remove existing file/symlink
if (unlink(output_path) != 0) {
print_error("Cannot remove '%s': %s", output_path, strerror(errno));
return -1;
}
}

// Read symlink target from container
char* target = malloc(entry->size + 1);
if (!target) {
print_error("Out of memory");
return -1;
}

size_t bytes_read = bfc_read(reader, entry->path, 0, target, entry->size);
if (bytes_read != entry->size) {
print_error("Failed to read symlink target for '%s'", entry->path);
free(target);
return -1;
}
target[entry->size] = '\0';

// Create symlink
if (symlink(target, output_path) != 0) {
print_error("Cannot create symlink '%s' -> '%s': %s", output_path, target, strerror(errno));
free(target);
return -1;
}

// Set timestamps using lutimes (for symlinks)
struct timeval times[2] = {
{.tv_sec = entry->mtime_ns / 1000000000ULL,
.tv_usec = (entry->mtime_ns % 1000000000ULL) / 1000}, // atime = mtime
{.tv_sec = entry->mtime_ns / 1000000000ULL,
.tv_usec = (entry->mtime_ns % 1000000000ULL) / 1000} // mtime
};

if (lutimes(output_path, times) != 0) {
print_verbose("Warning: cannot set timestamps on symlink '%s': %s", output_path,
strerror(errno));
}

if (!g_options.quiet) {
printf("Extracted: %s -> %s\n", output_path, target);
}

free(target);
return 0;
}

// Extract callback structure
typedef struct {
extract_options_t* opts;
Expand Down Expand Up @@ -363,6 +425,8 @@
result = extract_file(ctx->reader, entry, output_path, opts->force);
} else if (S_ISDIR(entry->mode)) {
result = extract_directory(output_path, entry, opts->force);
} else if (S_ISLNK(entry->mode)) {
result = extract_symlink(ctx->reader, entry, output_path, opts->force);
} else {
print_verbose("Skipping special file: %s", entry->path);
return 0;
Expand Down
9 changes: 8 additions & 1 deletion src/cli/cmd_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ typedef struct {
int total_entries;
int total_files;
int total_dirs;
int total_symlinks;
uint64_t total_size;
uint64_t total_compressed_size;
int show_detailed;
Expand All @@ -173,6 +174,8 @@ static int stats_callback(const bfc_entry_t* entry, void* user) {
ctx->total_compressed_size += entry->obj_size;
} else if (S_ISDIR(entry->mode)) {
ctx->total_dirs++;
} else if (S_ISLNK(entry->mode)) {
ctx->total_symlinks++;
}

if (ctx->show_detailed) {
Expand Down Expand Up @@ -213,7 +216,7 @@ static void show_container_info(bfc_t* reader, const char* container_file, int s
}

// Gather statistics
stats_context_t ctx = {0, 0, 0, 0, 0, show_detailed};
stats_context_t ctx = {0, 0, 0, 0, 0, 0, show_detailed};

if (show_detailed) {
printf("Entries:\n");
Expand All @@ -236,6 +239,9 @@ static void show_container_info(bfc_t* reader, const char* container_file, int s
printf(" Total entries: %d\n", ctx.total_entries);
printf(" Files: %d\n", ctx.total_files);
printf(" Directories: %d\n", ctx.total_dirs);
if (ctx.total_symlinks > 0) {
printf(" Symlinks: %d\n", ctx.total_symlinks);
}

if (has_encryption) {
printf(" Encryption: ChaCha20-Poly1305\n");
Expand Down Expand Up @@ -276,6 +282,7 @@ static void show_entry_info(bfc_t* reader, const char* path) {
printf("Entry: %s\n", entry.path);
printf("Type: %s\n", S_ISDIR(entry.mode) ? "Directory"
: S_ISREG(entry.mode) ? "Regular file"
: S_ISLNK(entry.mode) ? "Symlink"
: "Special file");
printf("Mode: %s (0%04o)\n", mode_str, entry.mode & 0777);
printf("Size: %s\n", size_str);
Expand Down
10 changes: 10 additions & 0 deletions src/cli/cmd_list.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ static void format_file_mode(uint32_t mode, char* buffer) {
buffer[0] = 'd';
else if (S_ISREG(mode))
buffer[0] = '-';
else if (S_ISLNK(mode))
buffer[0] = 'l';
else if (S_ISBLK(mode))
buffer[0] = 'b';
else if (S_ISCHR(mode))
buffer[0] = 'c';
else if (S_ISFIFO(mode))
buffer[0] = 'p';
else if (S_ISSOCK(mode))
buffer[0] = 's';
else
buffer[0] = '?';

Expand Down
Loading
Loading