From 642e2921241eeba1e36b5696e103907f2b75aefa Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 12:49:24 +0200 Subject: [PATCH 1/9] =?UTF-8?q?archivage=20et=20m=C3=A9tissage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 79 ++++++++ epitar/Makefile | 2 +- epitar/src/main.c | 39 +++- epitar/src/tar/archive.c | 133 ++++++++++++- epitar/src/tar/extract.c | 8 +- epitar/src/tar/tar.h | 3 + epitar/src/utils/config.c | 2 +- epitar/src/utils/errors.h | 3 + epitar/src/utils/filesystem.c | 100 ++++++++-- epitar/src/utils/filesystem.h | 36 ++++ epitar/tests/functional/run.sh | 329 +++++++++++++++++++++++++++++++++ 11 files changed, 696 insertions(+), 38 deletions(-) create mode 100644 .clang-format create mode 100644 epitar/tests/functional/run.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7ed8115 --- /dev/null +++ b/.clang-format @@ -0,0 +1,79 @@ +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: true + AfterClass: true + AfterControlStatement: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 80 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +FixNamespaceComments: true +ForEachMacros: ['ILIST_FOREACH', 'ILIST_FOREACH_ENTRY'] +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '<.*>' + Priority: 1 + - Regex: '.*' + Priority: 2 +IndentCaseLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +NamespaceIndentation: All +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never diff --git a/epitar/Makefile b/epitar/Makefile index b3430bf..8d614a7 100644 --- a/epitar/Makefile +++ b/epitar/Makefile @@ -27,7 +27,7 @@ debug: $(OBJS) $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) check: - dash ./tests/run.sh + bash ./tests/functional/run.sh clean: $(RM) $(TARGET) diff --git a/epitar/src/main.c b/epitar/src/main.c index 3e1f5d9..5126471 100644 --- a/epitar/src/main.c +++ b/epitar/src/main.c @@ -1,6 +1,7 @@ #include "tar/archive.h" #include "tar/extract.h" #include "utils/config.h" +#include "utils/errors.h" int main(int argc, char **argv) { @@ -31,22 +32,46 @@ int main(int argc, char **argv) err = archive(config.archive_file, config.input_files, config.verbose); } + int ret_code = 0; switch (err) { case SUCCESS: - return 0; + ret_code = 0; + break; - // case FILE_NOT_FOUND: - // case UNKNOWN_ERROR: - // printf("epitar: error extracting tarball %s\n", config.archive_file); - // return 3; + // case UNKNOWN_ERROR: + // printf("epitar: error extracting tarball %s\n", + // config.archive_file); ret_code = 3; break; case BAD_CHECKSUM: puts("epitar: bad checksum"); - return 2; + ret_code = 2; + break; + + case NOT_IMPLEMENTED: + puts("epitar: Function not implemented"); + ret_code = 2; + break; + + case FILE_NOT_FOUND: + if (config.mode == ARCHIVE) + { + puts("epitar: unable to add file tldr to tarball tata.tar"); + ret_code = 3; + break; + } + + case EMPTY_ARCHIVE: + puts("epitar: cowardly refusing to create an empty archive"); + ret_code = 1; + break; default: printf("epitar: error extracting tarball %s\n", config.archive_file); - return 3; + ret_code = 3; + break; } + + destroy_config(&config); + return ret_code; } diff --git a/epitar/src/tar/archive.c b/epitar/src/tar/archive.c index 014bcd1..dc2e88e 100644 --- a/epitar/src/tar/archive.c +++ b/epitar/src/tar/archive.c @@ -1,8 +1,131 @@ #include "archive.h" -enum error_code archive(char *archive_name, char **files, bool verbose) { - (void) archive_name; - (void) files; - (void) verbose; - return UNKNOWN_ERROR; +#include +#include +#include + +#include "../utils/filesystem.h" +#include "tar.h" + +/** + * + */ +static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) +{ + if (verbose) + puts(file_path); + + FILE *file = fopen(file_path, "r"); + if (file == NULL) + return FILE_NOT_FOUND; + // Create header (ustar) + struct ustar_header header = { 0 }; + strcpy(header.name, file_path); + // Length + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + snprintf(header.size, TSIZELEN, "%lu", file_size); + fseek(file, 0L, SEEK_SET); // Go back + + // TODO handle uid, gid, mtime, and links + + // Write header + int nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); + if (nwritten < sizeof(struct ustar_header)) + { + fclose(file); + return CANNOT_CREATE_TARGET; + } + + // Copy file content + fcat(file, archive, file_size); + + // Add padding + size_t total_size = + (((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE); + size_t padding = total_size - file_size; + + char empty_buf[512] = { 0 }; + size_t remaining = padding; + nwritten = fwrite(empty_buf, remaining, 1, archive); + if (nwritten < remaining) + { + fclose(file); + return CANNOT_CREATE_TARGET; + } + + return SUCCESS; +} + +/** + * @brief Archives a hole directory (recursively) + * @warn Assumes that the director path is valid + * @return SUCCESS or the corresponding error + */ +static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) +{ + // Add the directory itself + if (verbose) + puts(dir_path); + + // Archive members + char *filename; + while ((filename = listdirectory(dir_path)) != NULL) + { + enum error_code err; + enum error_code isdir = isdirectory(filename); + if (isdir == FILE_NOT_FOUND) + // Does not exists + err = FILE_NOT_FOUND; + else if (isdir == SUCCESS) + // Directory + err = archivedir(archive, filename, verbose); + else + // File + err = archivefile(archive, filename, verbose); + + if (err != SUCCESS) + { + freedirectory(); + return err; + } + } + + freedirectory(); // Just in case + return SUCCESS; +} + +enum error_code archive(char *archive_name, char **files, bool verbose) +{ + if (archive_name == NULL || files == NULL) + return UNKNOWN_ERROR; + if (*files == NULL) + return EMPTY_ARCHIVE; + + FILE *archive_stream = fopen(archive_name, "w+"); + + char **current_file = files; + while (*current_file != NULL) + { + enum error_code err; + enum error_code isdir = isdirectory(*current_file); + if (isdir == FILE_NOT_FOUND) + // Does not exists + err = FILE_NOT_FOUND; + else if (isdir == SUCCESS) + // Directory + err = archivedir(archive_stream, *current_file, verbose); + else + // File + err = archivefile(archive_stream, *current_file, verbose); + + if (err != SUCCESS) + { + fclose(archive_stream); + return err; + } + } + + fclose(archive_stream); + return SUCCESS; } diff --git a/epitar/src/tar/extract.c b/epitar/src/tar/extract.c index 1de4b9d..9c7ddbf 100644 --- a/epitar/src/tar/extract.c +++ b/epitar/src/tar/extract.c @@ -13,7 +13,6 @@ */ static enum error_code extract_ustar(FILE *stream, struct ustar_header *header) { - struct file_stats stats = { .name = header->name, .length = strtol(header->size, NULL, 8), .content_stream = stream, @@ -70,14 +69,14 @@ static enum error_code extract_file(FILE *content, union tar_header *header, if ((ustar_header = get_ustar_header(header)) != NULL) { if (verbose) - printf("./%s\n", ustar_header->name); + printf("%s\n", ustar_header->name); return extract_ustar(content, ustar_header); } // UNIXTAR else if ((unixtar_header = get_unixtar_header(header)) != NULL) { if (verbose) - printf("./%s", unixtar_header->name); + printf("%s\n", unixtar_header->name); return extract_unixtar(content, unixtar_header); } // Unsupported format @@ -140,7 +139,8 @@ enum error_code extract(char *archive_name, bool verbose) // Calculate how many padding bytes need to be skipped size_t file_size = strtol(header.ustar.size, NULL, 8); - size_t padded_size = ((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE; + size_t padded_size = + ((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE; size_t skip_bytes = padded_size - file_size; if (skip_bytes > 0 && fseek(stream, skip_bytes, SEEK_CUR) != 0) diff --git a/epitar/src/tar/tar.h b/epitar/src/tar/tar.h index ebf660c..8630b01 100644 --- a/epitar/src/tar/tar.h +++ b/epitar/src/tar/tar.h @@ -40,6 +40,9 @@ #define TOWRITE 00002 /* write by other */ #define TOEXEC 00001 /* execute/search by other */ +// Non standard definitions +#define TSIZELEN 12 /* Length of the size field */ + // === Structures struct ustar_header diff --git a/epitar/src/utils/config.c b/epitar/src/utils/config.c index e2d82c6..ba09793 100644 --- a/epitar/src/utils/config.c +++ b/epitar/src/utils/config.c @@ -203,5 +203,5 @@ void config_destroy(struct config *config) // Note: archive_file points to argv, so we don't free it - free(config); + // free(config); } diff --git a/epitar/src/utils/errors.h b/epitar/src/utils/errors.h index eaf81ea..78caf7c 100644 --- a/epitar/src/utils/errors.h +++ b/epitar/src/utils/errors.h @@ -6,11 +6,14 @@ enum error_code SUCCESS = 0, PENDING, UNKNOWN_ERROR, + GENERIC_ERROR, FILE_NOT_FOUND, BAD_CHECKSUM, INVALID_FORMAT, CANNOT_CREATE_TARGET, TARGET_ALREADY_EXISTS, + NO_TARGET_PROVIDED, + EMPTY_ARCHIVE, NOT_IMPLEMENTED }; diff --git a/epitar/src/utils/filesystem.c b/epitar/src/utils/filesystem.c index aac2b70..09becbc 100644 --- a/epitar/src/utils/filesystem.c +++ b/epitar/src/utils/filesystem.c @@ -1,8 +1,8 @@ #define _POSIX_C_SOURCE 200809L #include "filesystem.h" -#include "errors.h" +#include #include #include #include @@ -11,9 +11,15 @@ #include #include +#include "errors.h" + +static char *current_dir_path = NULL; +static DIR *current_dir = NULL; + enum error_code create_directory(struct file_stats *stats) { - if (stats == NULL || stats->name == NULL || stats->mode == NULL || stats->uid == NULL || stats->gid == NULL || stats->mtime == NULL) + if (stats == NULL || stats->name == NULL || stats->mode == NULL + || stats->uid == NULL || stats->gid == NULL || stats->mtime == NULL) return UNKNOWN_ERROR; // Create dir struct stat st = { 0 }; @@ -59,9 +65,9 @@ enum error_code create_link(struct file_stats *stats, char *target) if (errc != SUCCESS) return errc; // May change the target mode instead of the limk itsel - // also lchmod is not available on all systems so just ignoring this property here - // errc = setmode(stats->name, stats->mode); - // if (errc != SUCCESS) + // also lchmod is not available on all systems so just ignoring this + // property here errc = setmode(stats->name, stats->mode); if (errc != + // SUCCESS) // return errc; return SUCCESS; @@ -83,16 +89,7 @@ enum error_code create_file(struct file_stats *stats) if (file_stream == NULL) return CANNOT_CREATE_TARGET; // Copy content - char buffer[4096]; - size_t remaining = stats->length; - while (remaining > 0) { - size_t to_read = (remaining < sizeof(buffer)) ? remaining : sizeof(buffer); - size_t bytes_read = fread(buffer, 1, to_read, stats->content_stream); - if (bytes_read == 0) - break; // Reached EOF - fwrite(buffer, 1, bytes_read, file_stream); - remaining -= bytes_read; - } + fcat(stats->content_stream, file_stream, stats->length); // Close file fclose(file_stream); @@ -115,13 +112,13 @@ enum error_code create_file(struct file_stats *stats) enum error_code setowner(char *path, char *uid, char *gid) { if (path == NULL || uid == NULL || gid == NULL) - return UNKNOWN_ERROR; + return UNKNOWN_ERROR; - uid_t uid_val = strtol(uid, NULL, 10); - gid_t gid_val = strtol(gid, NULL, 10); + uid_t uid_val = strtol(uid, NULL, 10); + gid_t gid_val = strtol(gid, NULL, 10); - int err = chown(path, uid_val, gid_val); - return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; + int err = chown(path, uid_val, gid_val); + return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; } enum error_code setmtime(char *path, char *mtime) @@ -155,3 +152,66 @@ enum error_code setmode(char *path, char *mode) int err = chmod(path, mode_val); return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; } + +enum error_code isdirectory(char *path) +{ + struct stat st; + if (stat(path, &st) == -1) + return FILE_NOT_FOUND; + return S_ISREG(st.st_mode) ? SUCCESS : GENERIC_ERROR; +} + +char *listdirectory(char *path) +{ + struct dirent *de; + + // Check for path changes + if (current_dir_path != NULL && strcmp(current_dir_path, path) != 0) + { + // Close previous dir + freedirectory(); + // Open new dir + current_dir = opendir(current_dir_path); + if (current_dir == NULL) + { + freedirectory(); + return NULL; + } + + current_dir_path = strdup(path); + } + + // Get following file + de = readdir(current_dir); + if (de == NULL) + { + freedirectory(); + return NULL; + } + + return de->d_name; +} + +void freedirectory() +{ + if (current_dir != NULL) + closedir(current_dir); + if (current_dir_path != NULL) + free(current_dir_path); +} + +void fcat(FILE *src_stream, FILE *dst_stream, size_t size) +{ + char buffer[4096]; + size_t remaining = size; + while (remaining > 0) + { + size_t to_read = + (remaining < sizeof(buffer)) ? remaining : sizeof(buffer); + size_t bytes_read = fread(buffer, 1, to_read, src_stream); + if (bytes_read == 0) + break; // Reached EOF + fwrite(buffer, bytes_read, 1, dst_stream); + remaining -= bytes_read; + } +} diff --git a/epitar/src/utils/filesystem.h b/epitar/src/utils/filesystem.h index 6b16787..b504c4d 100644 --- a/epitar/src/utils/filesystem.h +++ b/epitar/src/utils/filesystem.h @@ -21,8 +21,44 @@ enum error_code create_directory(struct file_stats *stats); enum error_code create_link(struct file_stats *stats, char *target); enum error_code create_file(struct file_stats *stats); +void fcat(FILE *src_stream, FILE *dst_stream, size_t size); + enum error_code setowner(char *path, char *uid, char *gid); enum error_code setmtime(char *path, char *mtime); enum error_code setmode(char *path, char *mode); +/** + * @brief Retrieves the file informations at path and build the corresponding + * file_stats struct + * @arg path The given path + * @arg stats Pointer to where the function should write file informations + * @return FILE_NOT_FOUND if doesn's exists, GENERIC_ERROR if . + */ +enum error_code getstats(char *path, struct file_stats *stats); + +/** + * @brief Check if given path exists and is a directory + * @option path The given path + * @return FILE_NOT_FOUND if doesn's exists, GENERIC_ERROR if it is not a + * directory, SUCCESS otherwise. + */ +enum error_code isdirectory(char *path); + +/** + * @brief Lists a directory content. Returns a new member name after each + * iteration and NULL when there are no more to list. + * @arg path The directory path + * @return The current item name or NULL + * @warn The return item statically allocated and freed after finishing to list + * the directory or when freedirectory() is called. + */ +char *listdirectory(char *path); + +/** + * @brief Closes the last opened (and not closed) directory and frees the + * related allocated memory + * @warn Use this function if you only partially listed a directory previously. + */ +void freedirectory(); + #endif // FILESYSTEM_H diff --git a/epitar/tests/functional/run.sh b/epitar/tests/functional/run.sh new file mode 100644 index 0000000..37d5497 --- /dev/null +++ b/epitar/tests/functional/run.sh @@ -0,0 +1,329 @@ +#!/bin/sh + +# Testsuitator (mon bien-aimé) le retour <3 + +################### +# Variables # +################### + +executable="$BIN_PATH" +ref_executable="tar" + +total_tests=0 +errors_count=0 +timeouts_count=0 + +# Create temp directory for tests +test_dir=$(mktemp -d) +trap "rm -rf $test_dir" EXIT + + +################## +# Colors # +################## + +# Reset +Color_Off='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + + +################## +# Wrappers # +################## + +# @arg test name +# @arg command +check_result() { + local test_name="$1" + local actual_code="$2" + local ref_code="$3" + + test_failed=0 + + # Check return code + if [[ "$actual_code" -eq 124 ]]; then + echo -e $BRed "TIMEOUT" $Color_Off + echo -e ' ' "on '$test_name'" + echo -e ' ' "Expected code $ref_code but got $actual_code" + ((timeouts_count++)) + test_failed=1 + elif [[ "$actual_code" -ne "$ref_code" ]]; then + echo -e $BRed "FAILED" $Color_Off + echo -e ' ' "on '$test_name'" + echo -e ' ' "Expected code $ref_code but got $actual_code" + test_failed=1 + else + echo -e $BGreen "OK" $Color_Off + fi + + if [[ "$test_failed" -eq 1 ]]; then + ((errors_count++)) + fi +} + +# @arg test name +# @arg test command +test_archive_creation() { + local test_name="$1" + local archive_name="$2" + local input_files="$3" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + # Create test files + local test_subdir="$test_dir/$test_name" + mkdir -p "$test_subdir" + cd "$test_subdir" + + # Prepare input files + local file_list="" + for file in $input_files; do + echo "content of $file" > "$file" + file_list="$file_list $file" + done + + # Test with epitar + echo -e -n $Blue "= [ARCHIVE]" $Color_Off " " + timeout 2 $executable -c "$archive_name" $file_list > /dev/null 2>&1 + actual_code=$? + + if [[ $actual_code -eq 0 && -f "$archive_name" ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + # Test extraction + if [[ -f "$archive_name" ]]; then + echo -e -n $Blue "= [EXTRACT] " $Color_Off " " + extract_dir="extract_test" + mkdir "$extract_dir" + cd "$extract_dir" + timeout 2 $executable -x "../$archive_name" > /dev/null 2>&1 + actual_code=$? + + if [[ $actual_code -eq 0 ]]; then + # Check if files were extracted + all_extracted=1 + for file in $file_list; do + if [[ ! -f "$file" ]]; then + all_extracted=0 + fi + done + if [[ $all_extracted -eq 1 ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED (files not extracted)" $Color_Off + ((errors_count++)) + fi + else + echo -e $BRed "FAILED (exit code $actual_code)" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + cd .. + fi + + cd / + echo -e "\n" +} + +# @arg test name +# @arg files to test +test_verbose_output() { + local test_name="$1" + local files="$2" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + local test_subdir="$test_dir/${test_name}_verbose" + mkdir -p "$test_subdir" + cd "$test_subdir" + + # Create test files + for file in $files; do + echo "test content" > "$file" + done + + echo -e -n $Blue "= [VERBOSE]" $Color_Off " " + archive="test_verbose.tar" + timeout 2 $executable -cv "$archive" $files > output.txt 2>&1 + actual_code=$? + + # Check if output contains filenames + local all_found=1 + for file in $files; do + if ! grep -q "$file" output.txt; then + all_found=0 + fi + done + + if [[ $actual_code -eq 0 && $all_found -eq 1 ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + cd / + echo -e "\n" +} + +# @arg test name +test_help() { + local test_name="$1" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + echo -e -n $Blue "= [HELP]" $Color_Off " " + timeout 1 $executable -h > /dev/null 2>&1 + actual_code=$? + + if [[ $actual_code -eq 0 ]]; then + echo -e $BGreen "OK" $Color_Off + else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + echo -e "\n" +} + +# @arg test name +test_invalid_args() { + local test_name="$1" + local args="$2" + + echo -e $BBlue "========== $test_name ==========" $Color_Off + + echo -e -n $Blue "= [INVALID]" $Color_Off " " + timeout 1 $executable $args > /dev/null 2>&1 + actual_code=$? + + # Should fail + if [[ $actual_code -ne 0 ]]; then + echo -e $BGreen "OK (correctly rejected)" $Color_Off + else + echo -e $BRed "FAILED (should have been rejected)" $Color_Off + ((errors_count++)) + fi + ((total_tests++)) + + echo -e "\n" +} + +summarize() { + # Compute statistics + (( passed_tests = $total_tests - $errors_count )) + if [[ $total_tests -gt 0 ]]; then + (( tests_percentage = 100 * $passed_tests / $total_tests )) + else + tests_percentage=0 + fi + + # Adapt color depending on results + if [[ tests_percentage -gt 80 ]]; then + coverage_color=$BGreen + elif [[ tests_percentage -gt 50 ]]; then + coverage_color=$BYellow + else + coverage_color=$BRed + fi + + # Print + echo -e $BWhite "\n\n""===========" $BWhite"Summary"$Color_Off "\n" + echo -e " Passed $coverage_color$passed_tests/$total_tests$Color_Off tests ($coverage_color$tests_percentage%$Color_Off)" + echo -e " Got $timeouts_count timeout(s)" + if [ "$OUTPUT_FILE" != "" ]; then + echo $tests_percentage > "$OUTPUT_FILE" + fi + echo +} + + +################# +# Tests # +################# + +echo -e "\n\n""===$BGreen TestsuitatorX 2.0 Ultra Pro Max+ 365 Premium Gris Sidéral" "\n\n"$Color_Off + +echo -e "\n$BBlue=== Basic Operations ===$Color_Off" +test_archive_creation "Single file" "single.tar" "file1.txt" +test_archive_creation "Multiple files" "multi.tar" "file1.txt file2.txt file3.txt" +test_archive_creation "Files with spaces" "spaces.tar" "file_with_underscore.txt" + +echo -e "\n$BBlue=== Verbose Output ===$Color_Off" +test_verbose_output "Verbose creation" "file1.txt file2.txt" + +echo -e "\n$BBlue=== Help and Invalid Arguments ===$Color_Off" +test_help "Help flag" +test_invalid_args "No arguments" "" +test_invalid_args "Invalid flag" "-z" +test_invalid_args "Missing archive" "-c" +test_invalid_args "Extract with files" "-x archive.tar file.txt" + +echo -e "\n$BBlue=== Edge Cases ===$Color_Off" + +# Test with empty archive +test_subdir="$test_dir/empty_archive" +mkdir -p "$test_subdir" +cd "$test_subdir" +echo -e $BBlue "========== Empty archive ==========" $Color_Off +echo -e -n $Blue "= [CREATE_EMPTY]" $Color_Off " " +touch dummy +timeout 2 $executable -c "empty.tar" dummy > /dev/null 2>&1 +actual_code=$? +rm dummy +if [[ $actual_code -eq 0 ]]; then + echo -e $BGreen "OK" $Color_Off +else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) +fi +((total_tests++)) +cd / +echo -e "\n" + +# Test combined flags +test_subdir="$test_dir/combined_flags" +mkdir -p "$test_subdir" +cd "$test_subdir" +echo -e $BBlue "========== Combined flags (-cv) ==========" $Color_Off +echo -e -n $Blue "= [COMBINED]" $Color_Off " " +echo "content" > test.txt +timeout 2 $executable -cv "test.tar" test.txt > /dev/null 2>&1 +actual_code=$? +if [[ $actual_code -eq 0 && -f "test.tar" ]]; then + echo -e $BGreen "OK" $Color_Off +else + echo -e $BRed "FAILED" $Color_Off + ((errors_count++)) +fi +((total_tests++)) +cd / +echo -e "\n" + +summarize From 383f681156845e46bece4e65886571bb952c2ed0 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 18:43:30 +0200 Subject: [PATCH 2/9] Testsuite with ~50 tests and LOOOt of fixes (but seems to work now) --- epitar/Makefile | 2 + epitar/src/main.c | 4 +- epitar/src/tar/archive.c | 105 ++++++++++++++++++++--- epitar/src/tar/extract.c | 1 + epitar/src/tar/tar.h | 5 +- epitar/src/utils/config.c | 2 +- epitar/src/utils/config.h | 2 +- epitar/src/utils/filesystem.c | 52 +++++------ epitar/tests/functional/old_testsuite.sh | 91 ++++++++++++++++++++ epitar/tests/functional/warning.sh | 9 ++ 10 files changed, 226 insertions(+), 47 deletions(-) create mode 100644 epitar/tests/functional/old_testsuite.sh create mode 100644 epitar/tests/functional/warning.sh diff --git a/epitar/Makefile b/epitar/Makefile index 8d614a7..adbe911 100644 --- a/epitar/Makefile +++ b/epitar/Makefile @@ -27,7 +27,9 @@ debug: $(OBJS) $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) check: + bash ./tests/functional/old_testsuite.sh bash ./tests/functional/run.sh + bash ./tests/functional/warning.sh clean: $(RM) $(TARGET) diff --git a/epitar/src/main.c b/epitar/src/main.c index 5126471..1e12eb4 100644 --- a/epitar/src/main.c +++ b/epitar/src/main.c @@ -56,10 +56,12 @@ int main(int argc, char **argv) case FILE_NOT_FOUND: if (config.mode == ARCHIVE) { - puts("epitar: unable to add file tldr to tarball tata.tar"); + // Rest of code should have printed the first part of the line + printf(" to tarball %s\n", config.archive_file); ret_code = 3; break; } + __attribute__((fallthrough)); case EMPTY_ARCHIVE: puts("epitar: cowardly refusing to create an empty archive"); diff --git a/epitar/src/tar/archive.c b/epitar/src/tar/archive.c index dc2e88e..749f239 100644 --- a/epitar/src/tar/archive.c +++ b/epitar/src/tar/archive.c @@ -7,8 +7,63 @@ #include "../utils/filesystem.h" #include "tar.h" +static size_t getfilesize(FILE *file) +{ + fseek(file, 0L, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0L, SEEK_SET); // Go back + return file_size; +} + /** - * + * @brief Creates and returns the header for the file located at `file_path` and + * with content `file_stream` + * @param file_stream Pointer to the content of the file (as a stream) if it's a + * file. If it's NULL file will be considered as a directory + */ +static struct ustar_header create_header(FILE *file_stream, char *file_path) +{ + struct ustar_header header = { 0 }; + + // Name + strncpy(header.name, file_path, TNAMELEN); + // Magic tag + strncpy(header.magic, USTAR_MAGIC, TMAGLEN); + // Versions + memcpy(header.version, TVERSION, TVERSLEN); + + // TODO handle uid, gid, mtime, and links + + // File or directory + if (file_stream == NULL) + { + // === Directory + // Type + header.typeflag = DIRTYPE; + } + else + { + // === File + // Length + size_t file_size = getfilesize(file_stream); + snprintf(header.size, TSIZELEN, "%lu", file_size); + // Type + header.typeflag = REGTYPE; + } + + // Checksum + union tar_header header_union = { .ustar = header }; + size_t chksum = compute_checksum(&header_union); + snprintf(header.chksum, TCHKSUMLEN, "%lu", chksum); + + return header; +} + +/** + * @brief Adds a file (header + content) to the archive stream + * @param archive The target archive stream + * @param file_path The path where the file to be archived is located + * @return SUCCESS or the corresponding error code */ static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) { @@ -17,21 +72,18 @@ static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) FILE *file = fopen(file_path, "r"); if (file == NULL) + { + printf("Unable to add file %s", file_path); return FILE_NOT_FOUND; - // Create header (ustar) - struct ustar_header header = { 0 }; - strcpy(header.name, file_path); - // Length - fseek(file, 0L, SEEK_END); - size_t file_size = ftell(file); - snprintf(header.size, TSIZELEN, "%lu", file_size); - fseek(file, 0L, SEEK_SET); // Go back + } - // TODO handle uid, gid, mtime, and links + // Create header (ustar) + struct ustar_header header = create_header(file, file_path); + size_t file_size = getfilesize(file); // Write header - int nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); - if (nwritten < sizeof(struct ustar_header)) + size_t nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); + if (nwritten < 1) { fclose(file); return CANNOT_CREATE_TARGET; @@ -45,9 +97,9 @@ static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) (((file_size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE); size_t padding = total_size - file_size; - char empty_buf[512] = { 0 }; + char empty_buf[BLOCK_SIZE] = { 0 }; size_t remaining = padding; - nwritten = fwrite(empty_buf, remaining, 1, archive); + nwritten = fwrite(empty_buf, sizeof(char), remaining, archive); if (nwritten < remaining) { fclose(file); @@ -68,6 +120,12 @@ static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) if (verbose) puts(dir_path); + // Header + struct ustar_header header = create_header(NULL, dir_path); + size_t nwritten = fwrite(&header, sizeof(struct ustar_header), 1, archive); + if (nwritten < 1) + return CANNOT_CREATE_TARGET; + // Archive members char *filename; while ((filename = listdirectory(dir_path)) != NULL) @@ -75,14 +133,21 @@ static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) enum error_code err; enum error_code isdir = isdirectory(filename); if (isdir == FILE_NOT_FOUND) + { // Does not exists + printf("Unable to add file %s", dir_path); err = FILE_NOT_FOUND; + } else if (isdir == SUCCESS) + { // Directory err = archivedir(archive, filename, verbose); + } else + { // File err = archivefile(archive, filename, verbose); + } if (err != SUCCESS) { @@ -110,22 +175,34 @@ enum error_code archive(char *archive_name, char **files, bool verbose) enum error_code err; enum error_code isdir = isdirectory(*current_file); if (isdir == FILE_NOT_FOUND) + { // Does not exists + printf("Unable to add file %s", *current_file); err = FILE_NOT_FOUND; + } else if (isdir == SUCCESS) + { // Directory err = archivedir(archive_stream, *current_file, verbose); + } else + { // File err = archivefile(archive_stream, *current_file, verbose); + } if (err != SUCCESS) { fclose(archive_stream); return err; } + + current_file++; } + // Terminating NULL blocks + char empty_buf[BLOCK_SIZE * 2] = { 0 }; + fwrite(empty_buf, sizeof(char), BLOCK_SIZE * 2, archive_stream); fclose(archive_stream); return SUCCESS; } diff --git a/epitar/src/tar/extract.c b/epitar/src/tar/extract.c index 9c7ddbf..77ffcbd 100644 --- a/epitar/src/tar/extract.c +++ b/epitar/src/tar/extract.c @@ -112,6 +112,7 @@ enum error_code extract(char *archive_name, bool verbose) if (bytes_read < sizeof(union tar_header)) { read_state = INVALID_FORMAT; // Unexpected EOF + continue; } // Check if empty diff --git a/epitar/src/tar/tar.h b/epitar/src/tar/tar.h index 8630b01..db62b12 100644 --- a/epitar/src/tar/tar.h +++ b/epitar/src/tar/tar.h @@ -41,7 +41,9 @@ #define TOEXEC 00001 /* execute/search by other */ // Non standard definitions +#define TNAMELEN 100 /* Length of the name field */ #define TSIZELEN 12 /* Length of the size field */ +#define TCHKSUMLEN 8 /* Length of the size field */ // === Structures @@ -63,6 +65,7 @@ struct ustar_header char devmajor[8]; char devminor[8]; char prefix[155]; + char padding[12]; }; struct unixtar_header @@ -74,7 +77,7 @@ struct unixtar_header char size[12]; char mtime[12]; char chksum[8]; - char link[1]; + char link; char linkname[100]; char padding[255]; // Should be empty }; diff --git a/epitar/src/utils/config.c b/epitar/src/utils/config.c index ba09793..45e4dfb 100644 --- a/epitar/src/utils/config.c +++ b/epitar/src/utils/config.c @@ -190,7 +190,7 @@ void print_invalid_option(void) puts("Try './epitar -h' for more information"); } -void config_destroy(struct config *config) +void destroy_config(struct config *config) { if (config == NULL) return; diff --git a/epitar/src/utils/config.h b/epitar/src/utils/config.h index c429c96..a79bfc9 100644 --- a/epitar/src/utils/config.h +++ b/epitar/src/utils/config.h @@ -55,7 +55,7 @@ void print_invalid_option(void); */ bool validate_config(struct config *cfg); -/* +/* @brief frees all data allocated by config */ void destroy_config(struct config *config); diff --git a/epitar/src/utils/filesystem.c b/epitar/src/utils/filesystem.c index 09becbc..6f3f237 100644 --- a/epitar/src/utils/filesystem.c +++ b/epitar/src/utils/filesystem.c @@ -32,17 +32,13 @@ enum error_code create_directory(struct file_stats *stats) } else { - return TARGET_ALREADY_EXISTS; + // return TARGET_ALREADY_EXISTS; + return SUCCESS; } // Set stats - enum error_code errc; - errc = setowner(stats->name, stats->uid, stats->gid); - if (errc != SUCCESS) - return errc; - errc = setmtime(stats->name, stats->mtime); - if (errc != SUCCESS) - return errc; + setowner(stats->name, stats->uid, stats->gid); + setmtime(stats->name, stats->mtime); return SUCCESS; } @@ -57,13 +53,8 @@ enum error_code create_link(struct file_stats *stats, char *target) return CANNOT_CREATE_TARGET; // Set stats - enum error_code errc; - errc = setowner(stats->name, stats->uid, stats->gid); - if (errc != SUCCESS) - return errc; - errc = setmtime(stats->name, stats->mtime); - if (errc != SUCCESS) - return errc; + setowner(stats->name, stats->uid, stats->gid); + setmtime(stats->name, stats->mtime); // May change the target mode instead of the limk itsel // also lchmod is not available on all systems so just ignoring this // property here errc = setmode(stats->name, stats->mode); if (errc != @@ -95,16 +86,9 @@ enum error_code create_file(struct file_stats *stats) fclose(file_stream); // Set stats - enum error_code errc; - errc = setowner(stats->name, stats->uid, stats->gid); - if (errc != SUCCESS) - return errc; - errc = setmtime(stats->name, stats->mtime); - if (errc != SUCCESS) - return errc; - errc = setmode(stats->name, stats->mode); - if (errc != SUCCESS) - return errc; + setowner(stats->name, stats->uid, stats->gid); + setmtime(stats->name, stats->mtime); + setmode(stats->name, stats->mode); return SUCCESS; } @@ -158,7 +142,7 @@ enum error_code isdirectory(char *path) struct stat st; if (stat(path, &st) == -1) return FILE_NOT_FOUND; - return S_ISREG(st.st_mode) ? SUCCESS : GENERIC_ERROR; + return S_ISREG(st.st_mode) ? GENERIC_ERROR : SUCCESS; } char *listdirectory(char *path) @@ -166,19 +150,19 @@ char *listdirectory(char *path) struct dirent *de; // Check for path changes - if (current_dir_path != NULL && strcmp(current_dir_path, path) != 0) + if (current_dir == NULL || current_dir_path == NULL + || strcmp(current_dir_path, path) != 0) { // Close previous dir freedirectory(); // Open new dir + current_dir_path = strdup(path); current_dir = opendir(current_dir_path); if (current_dir == NULL) { freedirectory(); return NULL; } - - current_dir_path = strdup(path); } // Get following file @@ -189,15 +173,25 @@ char *listdirectory(char *path) return NULL; } + // Check for '.' or '..' + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) + return listdirectory(path); + return de->d_name; } void freedirectory() { if (current_dir != NULL) + { closedir(current_dir); + current_dir = NULL; + } if (current_dir_path != NULL) + { free(current_dir_path); + current_dir_path = NULL; + } } void fcat(FILE *src_stream, FILE *dst_stream, size_t size) diff --git a/epitar/tests/functional/old_testsuite.sh b/epitar/tests/functional/old_testsuite.sh new file mode 100644 index 0000000..5e16a27 --- /dev/null +++ b/epitar/tests/functional/old_testsuite.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +# Old testsuite but still pertinent + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +PASSED=0 +FAILED=0 +TOTAL=0 + +# Ensure executable exists +if [ ! -x "./epitar" ]; then + echo -e "${RED}Error: ./epitar not found or not executable.${NC}" + exit 1 +fi + +# assert "test name" "command" "expected_exit_code" "expected_output_substring" +assert() { + ((TOTAL++)) + local label=$1 + local cmd=$2 + local expected_code=$3 + local expected_msg=$4 + + echo -n "Test $TOTAL: $label... " + + # Run + output=$(eval "$cmd" 2>&1) + exit_code=$? + + if [ "$exit_code" -eq "$expected_code" ] && [[ "$output" == *"$expected_msg"* ]]; then + echo -e "${GREEN}PASS${NC}" + ((PASSED++)) + else + echo -e "${RED}FAIL${NC}" + echo -e " Expected code: $expected_code, Got: $exit_code" + echo -e " Expected msg contains: '$expected_msg'" + echo -e " Actual output: '$output'" + ((FAILED++)) + fi +} + +# === Clean Environment === +rm -f *.tar tmp_file1 tmp_file2 +touch tmp_file1 tmp_file2 +echo "corrupt data" > bad_checksum.tar + +# === Testsuite === + +echo -e "${BLUE}Starting epitar test suite...${NC}\n" + +# Help and Basic Usage +assert "Help flag -h" "./epitar -h" 0 "Usage: epitar -[xcvh] []" +assert "No arguments" "./epitar" 1 "invalid or missing option" +assert "Invalid flag -z" "./epitar -z" 1 "invalid or missing option" +assert "Invalid flag -a" "./epitar -a toto.tar titi" 1 "invalid or missing option" + +# Creation Errors (-c) +assert "Refuse empty archive" "./epitar -c empty.tar" 1 "cowardly refusing" +assert "Missing archive name for -c" "./epitar -c" 1 "invalid or missing option" +assert "Archive adding itself" "./epitar -c self.tar self.tar" 0 "is the archive; not dumped" +assert "Unable to add missing file" "./epitar -cv missing.tar non_existent_file" 3 "unable to add file" + +# Extraction Errors (-x) +assert "Bad checksum" "./epitar -x bad_checksum.tar" 2 "bad checksum" +assert "Extraction of non-existent file" "./epitar -x nowhere.tar" 3 "error extracting tarball" +assert "Missing archive name for -x" "./epitar -x" 1 "invalid or missing option" + +# Combinations & Complex flags +assert "Multiple invalid flags" "./epitar -xz archive.tar" 1 "invalid or missing option" +assert "Help priority" "./epitar -h -x nonexistent.tar" 0 "Usage:" +assert "Multiple files for creation (failure case)" "./epitar -c fail.tar tmp_file1 missing_file" 3 "unable to add file" + +# Path and Environment edge cases +assert "Relative path to non-existent" "./epitar -x ./subdir/fake.tar" 3 "error extracting tarball" +assert "Creation with dot (.)" "./epitar -c current.tar ." 0 "current.tar is the archive; not dumped" +assert "List content (hypothetical -v without -c or -x)" "./epitar -v" 1 "invalid or missing option" +assert "Wrong order of arguments" "./epitar archive.tar -c" 1 "invalid or missing option" + +# === Summary === +echo -e "\n------------------------------------" +echo -e "Results: ${GREEN}$PASSED Passed${NC}, ${RED}$FAILED Failed${NC} / $TOTAL Total" +echo -e "------------------------------------" + +# Cleanup +rm -f *.tar tmp_file1 tmp_file2 + diff --git a/epitar/tests/functional/warning.sh b/epitar/tests/functional/warning.sh new file mode 100644 index 0000000..6b66c3b --- /dev/null +++ b/epitar/tests/functional/warning.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +Color_Off='\033[0m' # Text Reset +BYellow='\033[1;33m' # Yellow + +echo +echo -e $BYellow "WARNING:" $Color_Off " Two testsuites have been run, don't just check the last summary" +echo -e $BYellow "WARNING:" $Color_Off " Archive/Extract tests will be partly hidden if their first part doesn't succeed" +echo " (So if you want the accurate number of tests just add the number of failed tests to the total)" From 589117ae1ada91b55e56516a6b6c77a34104b2a9 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 18:45:51 +0200 Subject: [PATCH 3/9] missing dep in make check --- epitar/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epitar/Makefile b/epitar/Makefile index adbe911..19a4021 100644 --- a/epitar/Makefile +++ b/epitar/Makefile @@ -26,7 +26,7 @@ debug: LDFLAGS += $(DBG_LDFLAGS) debug: $(OBJS) $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) -check: +check: $(TARGET) bash ./tests/functional/old_testsuite.sh bash ./tests/functional/run.sh bash ./tests/functional/warning.sh From d71848194fa9f1b5ad7c7ee9a9f397ae4622d59c Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 19:57:58 +0200 Subject: [PATCH 4/9] TAR COMPATIBILITYYYYYYYYYYYY --- epitar/src/tar/archive.c | 45 ++++++++++++++++++++++++++++++-------- epitar/src/tar/tar.c | 46 +++++++++++++++++++++++++++++---------- epitar/src/tar/tar.h | 7 +++--- epitar/src/utils/errors.h | 1 + 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/epitar/src/tar/archive.c b/epitar/src/tar/archive.c index 749f239..00ae94d 100644 --- a/epitar/src/tar/archive.c +++ b/epitar/src/tar/archive.c @@ -1,8 +1,11 @@ #include "archive.h" +#include +#include #include #include #include +#include #include "../utils/filesystem.h" #include "tar.h" @@ -32,31 +35,55 @@ static struct ustar_header create_header(FILE *file_stream, char *file_path) // Versions memcpy(header.version, TVERSION, TVERSLEN); - // TODO handle uid, gid, mtime, and links + // uid, gid, mtime and mode + struct stat st; + if (stat(file_path, &st) == -1) + { + printf("Could not stat file %s\n", file_path); + return header; + } + // TODO links + // UID + snprintf(header.uid, sizeof(header.uid), "%07o", st.st_uid); + // GID + snprintf(header.gid, sizeof(header.gid), "%07o", st.st_gid); + // Mtime + snprintf(header.mtime, sizeof(header.mtime), "%011lo", st.st_mtime); + // Mode + snprintf(header.mode, sizeof(header.mode), "%07o", st.st_mode & 0777); + + // User name + struct passwd *pw = getpwuid(st.st_uid); + if (pw != NULL) + strncpy(header.uname, pw->pw_name, 32); + // Group name + struct group *gr = getgrgid(st.st_gid); + if (gr != NULL) + strncpy(header.gname, gr->gr_name, 32); // File or directory if (file_stream == NULL) { // === Directory + header.name[strlen(header.name)] = '/'; // Type header.typeflag = DIRTYPE; + // Size + snprintf(header.size, 12, "%011o", 0); } else { // === File - // Length - size_t file_size = getfilesize(file_stream); - snprintf(header.size, TSIZELEN, "%lu", file_size); + // Size + snprintf(header.size, 12, "%011lo", st.st_size); // Type header.typeflag = REGTYPE; } // Checksum union tar_header header_union = { .ustar = header }; - size_t chksum = compute_checksum(&header_union); - snprintf(header.chksum, TCHKSUMLEN, "%lu", chksum); - - return header; + compute_checksum(&header_union); + return header_union.ustar; } /** @@ -118,7 +145,7 @@ static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) { // Add the directory itself if (verbose) - puts(dir_path); + printf("%s/\n", dir_path); // Header struct ustar_header header = create_header(NULL, dir_path); diff --git a/epitar/src/tar/tar.c b/epitar/src/tar/tar.c index 5defa56..ee9fd7a 100644 --- a/epitar/src/tar/tar.c +++ b/epitar/src/tar/tar.c @@ -1,6 +1,6 @@ #include "tar.h" -#include +#include #include struct unixtar_header *get_unixtar_header(union tar_header *header) @@ -26,34 +26,58 @@ bool check_checksum(union tar_header *header) { struct ustar_header *ustar_header; struct unixtar_header *unixtar_header; - int actual_checksum; + char actual_checksum[TCHKSUMLEN]; // USTAR if ((ustar_header = get_ustar_header(header)) != NULL) { - actual_checksum = atoi(ustar_header->chksum); + strcpy(actual_checksum, ustar_header->chksum); + return strcmp(actual_checksum, header->ustar.chksum) == 0; } // UNIXTAR else if ((unixtar_header = get_unixtar_header(header)) != NULL) { - actual_checksum = atoi(unixtar_header->chksum); + strcpy(actual_checksum, unixtar_header->chksum); + return strcmp(actual_checksum, header->unixtar.chksum) == 0; } // Unsupported format else { return false; } - - int computed_checksum = compute_checksum(header); - return computed_checksum == actual_checksum; } -int compute_checksum(union tar_header *header) +void compute_checksum(union tar_header *header) { - char *block = header->raw_block; - int sum = 0; + // Set to spaces + struct unixtar_header *unixtar_header = get_unixtar_header(header); + struct ustar_header *ustar_header = get_ustar_header(header); + if (ustar_header) + { + memset(ustar_header->chksum, ' ', TCHKSUMLEN); + } + else if (unixtar_header) + { + memset(unixtar_header->chksum, ' ', TCHKSUMLEN); + } + + // Compute + unsigned char *block = header->raw_block; + unsigned int sum = 0; for (int i = 0; i < BLOCK_SIZE; i++) sum += block[i]; - return sum; + // Update checksum + if (ustar_header) + { + snprintf(ustar_header->chksum, TCHKSUMLEN - 1, "%06o", sum); + ustar_header->chksum[TCHKSUMLEN - 2] = '\0'; + ustar_header->chksum[TCHKSUMLEN - 1] = ' '; + } + else if (unixtar_header) + { + snprintf(unixtar_header->chksum, TCHKSUMLEN - 1, "%06o", sum); + unixtar_header->chksum[TCHKSUMLEN - 2] = '\0'; + unixtar_header->chksum[TCHKSUMLEN - 1] = ' '; + } } diff --git a/epitar/src/tar/tar.h b/epitar/src/tar/tar.h index db62b12..82dab5f 100644 --- a/epitar/src/tar/tar.h +++ b/epitar/src/tar/tar.h @@ -86,7 +86,7 @@ union tar_header { struct unixtar_header unixtar; struct ustar_header ustar; - char raw_block[BLOCK_SIZE]; + unsigned char raw_block[BLOCK_SIZE]; }; // === Functions @@ -103,13 +103,14 @@ struct unixtar_header *get_unixtar_header(union tar_header *header); struct ustar_header *get_ustar_header(union tar_header *header); /* @brief checks if file has not been corrupted via checksum + * @warn Alters header checksum with the new one * @return true if member file is valid, false otherwise */ bool check_checksum(union tar_header *header); -/* @brief computes the checksum of the given file +/* @brief computes the checksum of the given header and updates it * @return the checksum */ -int compute_checksum(union tar_header *header); +void compute_checksum(union tar_header *header); #endif // TAR_H diff --git a/epitar/src/utils/errors.h b/epitar/src/utils/errors.h index 78caf7c..aeb20c3 100644 --- a/epitar/src/utils/errors.h +++ b/epitar/src/utils/errors.h @@ -14,6 +14,7 @@ enum error_code TARGET_ALREADY_EXISTS, NO_TARGET_PROVIDED, EMPTY_ARCHIVE, + CANNOT_STAT, NOT_IMPLEMENTED }; From 1e79def6997d7f95e115353618a4d12d231c69dc Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 20:00:28 +0200 Subject: [PATCH 5/9] wtf srx la mouli --- epitar/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/epitar/Makefile b/epitar/Makefile index 19a4021..52c4801 100644 --- a/epitar/Makefile +++ b/epitar/Makefile @@ -21,6 +21,9 @@ $(TARGET): $(OBJS) $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS) @echo $(OBJS) +all: $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS) + debug: CFLAGS += $(DBG_CFLAGS) debug: LDFLAGS += $(DBG_LDFLAGS) debug: $(OBJS) From 8ec0d9a4ee0765ef6602831c8fc76732725b9fb0 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 20:09:28 +0200 Subject: [PATCH 6/9] fix clang tidy et format --- epitar/src/main.c | 89 ++++++++++++++++++----------------- epitar/src/tar/archive.h | 4 +- epitar/src/tar/extract.c | 11 +++-- epitar/src/utils/filesystem.c | 2 +- epitar/src/utils/filesystem.h | 2 +- epitar/src/utils/misc.c | 3 -- epitar/src/utils/misc.h | 6 --- 7 files changed, 59 insertions(+), 58 deletions(-) delete mode 100644 epitar/src/utils/misc.c delete mode 100644 epitar/src/utils/misc.h diff --git a/epitar/src/main.c b/epitar/src/main.c index 1e12eb4..7c84e2d 100644 --- a/epitar/src/main.c +++ b/epitar/src/main.c @@ -3,6 +3,52 @@ #include "utils/config.h" #include "utils/errors.h" +static int handle_err_code(enum error_code err, struct config *config) +{ + int ret_code = 0; + switch (err) + { + case SUCCESS: + ret_code = 0; + break; + + // case UNKNOWN_ERROR: + // printf("epitar: error extracting tarball %s\n", + // config.archive_file); ret_code = 3; break; + + case BAD_CHECKSUM: + puts("epitar: bad checksum"); + ret_code = 2; + break; + + case NOT_IMPLEMENTED: + puts("epitar: Function not implemented"); + ret_code = 2; + break; + + case FILE_NOT_FOUND: + if (config->mode == ARCHIVE) + { + // Rest of code should have printed the first part of the line + printf(" to tarball %s\n", config->archive_file); + ret_code = 3; + break; + } + __attribute__((fallthrough)); + + case EMPTY_ARCHIVE: + puts("epitar: cowardly refusing to create an empty archive"); + ret_code = 1; + break; + + default: + printf("epitar: error extracting tarball %s\n", config->archive_file); + ret_code = 3; + break; + } + return ret_code; +} + int main(int argc, char **argv) { // Handle args @@ -32,48 +78,7 @@ int main(int argc, char **argv) err = archive(config.archive_file, config.input_files, config.verbose); } - int ret_code = 0; - switch (err) - { - case SUCCESS: - ret_code = 0; - break; - - // case UNKNOWN_ERROR: - // printf("epitar: error extracting tarball %s\n", - // config.archive_file); ret_code = 3; break; - - case BAD_CHECKSUM: - puts("epitar: bad checksum"); - ret_code = 2; - break; - - case NOT_IMPLEMENTED: - puts("epitar: Function not implemented"); - ret_code = 2; - break; - - case FILE_NOT_FOUND: - if (config.mode == ARCHIVE) - { - // Rest of code should have printed the first part of the line - printf(" to tarball %s\n", config.archive_file); - ret_code = 3; - break; - } - __attribute__((fallthrough)); - - case EMPTY_ARCHIVE: - puts("epitar: cowardly refusing to create an empty archive"); - ret_code = 1; - break; - - default: - printf("epitar: error extracting tarball %s\n", config.archive_file); - ret_code = 3; - break; - } - + int ret_code = handle_err_code(err, &config); destroy_config(&config); return ret_code; } diff --git a/epitar/src/tar/archive.h b/epitar/src/tar/archive.h index 546f4ff..f9dd21f 100644 --- a/epitar/src/tar/archive.h +++ b/epitar/src/tar/archive.h @@ -1,10 +1,10 @@ #ifndef ARCHIVE_H #define ARCHIVE_H -#include "../utils/errors.h" - #include +#include "../utils/errors.h" + /* @brief Archives the listed `files` into `archive_name` * @return 0 on success, the corresponding error code otherwise */ diff --git a/epitar/src/tar/extract.c b/epitar/src/tar/extract.c index 77ffcbd..b085ef5 100644 --- a/epitar/src/tar/extract.c +++ b/epitar/src/tar/extract.c @@ -49,9 +49,14 @@ static enum error_code extract_ustar(FILE *stream, struct ustar_header *header) static enum error_code extract_unixtar(FILE *stream, struct unixtar_header *header) { - (void)stream; - (void)header; - return NOT_IMPLEMENTED; + struct file_stats stats = { .name = header->name, + .length = strtol(header->size, NULL, 8), + .content_stream = stream, + .mode = header->mode, + .uid = header->uid, + .gid = header->gid, + .mtime = header->mtime }; + return create_file(&stats); } /** diff --git a/epitar/src/utils/filesystem.c b/epitar/src/utils/filesystem.c index 6f3f237..9116034 100644 --- a/epitar/src/utils/filesystem.c +++ b/epitar/src/utils/filesystem.c @@ -180,7 +180,7 @@ char *listdirectory(char *path) return de->d_name; } -void freedirectory() +void freedirectory(void) { if (current_dir != NULL) { diff --git a/epitar/src/utils/filesystem.h b/epitar/src/utils/filesystem.h index b504c4d..160c800 100644 --- a/epitar/src/utils/filesystem.h +++ b/epitar/src/utils/filesystem.h @@ -59,6 +59,6 @@ char *listdirectory(char *path); * related allocated memory * @warn Use this function if you only partially listed a directory previously. */ -void freedirectory(); +void freedirectory(void); #endif // FILESYSTEM_H diff --git a/epitar/src/utils/misc.c b/epitar/src/utils/misc.c deleted file mode 100644 index 0271150..0000000 --- a/epitar/src/utils/misc.c +++ /dev/null @@ -1,3 +0,0 @@ -#include "utils.h" - -#include diff --git a/epitar/src/utils/misc.h b/epitar/src/utils/misc.h deleted file mode 100644 index 3d20ecc..0000000 --- a/epitar/src/utils/misc.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef MISC_H -#define MISC_H - -void print_str_array(const char **input_files); - -#endif /* MISC_H */ From c6408716d5d4695696b6537c2141a263e779276e Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 21:25:12 +0200 Subject: [PATCH 7/9] more fixes, et je crois que la nouvelle testsuite est kc --- epitar/src/main.c | 17 +++++++++++------ epitar/src/tar/archive.c | 6 +++--- epitar/src/tar/extract.c | 7 +++++++ epitar/src/utils/config.c | 2 +- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/epitar/src/main.c b/epitar/src/main.c index 7c84e2d..92171d5 100644 --- a/epitar/src/main.c +++ b/epitar/src/main.c @@ -12,10 +12,6 @@ static int handle_err_code(enum error_code err, struct config *config) ret_code = 0; break; - // case UNKNOWN_ERROR: - // printf("epitar: error extracting tarball %s\n", - // config.archive_file); ret_code = 3; break; - case BAD_CHECKSUM: puts("epitar: bad checksum"); ret_code = 2; @@ -37,8 +33,17 @@ static int handle_err_code(enum error_code err, struct config *config) __attribute__((fallthrough)); case EMPTY_ARCHIVE: - puts("epitar: cowardly refusing to create an empty archive"); - ret_code = 1; + if (config->mode == ARCHIVE) + { + puts("epitar: cowardly refusing to create an empty archive"); + ret_code = 1; + } + else + { + printf("epitar: error extracting tarball %s\n", + config->archive_file); + ret_code = 3; + } break; default: diff --git a/epitar/src/tar/archive.c b/epitar/src/tar/archive.c index 00ae94d..ed0b247 100644 --- a/epitar/src/tar/archive.c +++ b/epitar/src/tar/archive.c @@ -100,7 +100,7 @@ static enum error_code archivefile(FILE *archive, char *file_path, bool verbose) FILE *file = fopen(file_path, "r"); if (file == NULL) { - printf("Unable to add file %s", file_path); + printf("unable to add file %s", file_path); return FILE_NOT_FOUND; } @@ -162,7 +162,7 @@ static enum error_code archivedir(FILE *archive, char *dir_path, bool verbose) if (isdir == FILE_NOT_FOUND) { // Does not exists - printf("Unable to add file %s", dir_path); + printf("unable to add file %s", dir_path); err = FILE_NOT_FOUND; } else if (isdir == SUCCESS) @@ -204,7 +204,7 @@ enum error_code archive(char *archive_name, char **files, bool verbose) if (isdir == FILE_NOT_FOUND) { // Does not exists - printf("Unable to add file %s", *current_file); + printf("unable to add file %s", *current_file); err = FILE_NOT_FOUND; } else if (isdir == SUCCESS) diff --git a/epitar/src/tar/extract.c b/epitar/src/tar/extract.c index b085ef5..28f2677 100644 --- a/epitar/src/tar/extract.c +++ b/epitar/src/tar/extract.c @@ -5,6 +5,7 @@ #include #include "../utils/filesystem.h" +#include "tar.h" /** * @@ -135,6 +136,12 @@ enum error_code extract(char *archive_name, bool verbose) got_empty_block = false; } + if (!check_checksum(&header)) + { + fclose(stream); + return BAD_CHECKSUM; + } + // Extract file enum error_code err = extract_file(stream, &header, verbose); if (err != SUCCESS) diff --git a/epitar/src/utils/config.c b/epitar/src/utils/config.c index 45e4dfb..a46a5a7 100644 --- a/epitar/src/utils/config.c +++ b/epitar/src/utils/config.c @@ -81,7 +81,7 @@ enum arg_error_code args_handler(int argc, char **argv, struct config *config) // Parse option flags int opt; - while ((opt = getopt(argc, argv, "cxvh")) != -1) + while ((opt = getopt(argc, argv, ":cvxh")) != -1) { int err = handle_opt(opt, config); if (err == ARG_HELP) From 2c07b3f2f9493bd50809dad97e6d60d893ed14d3 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 21:26:58 +0200 Subject: [PATCH 8/9] urgent Makefile fix --- epitar/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epitar/Makefile b/epitar/Makefile index 52c4801..10068ef 100644 --- a/epitar/Makefile +++ b/epitar/Makefile @@ -22,7 +22,7 @@ $(TARGET): $(OBJS) @echo $(OBJS) all: $(OBJS) - $(CC) -o $@ $^ $(LDFLAGS) $(LDLIBS) + $(CC) -o $(TARGET) $^ $(LDFLAGS) $(LDLIBS) debug: CFLAGS += $(DBG_CFLAGS) debug: LDFLAGS += $(DBG_LDFLAGS) From ca10e8aac85900db2b1754a87d99051bda1182a4 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 8 Apr 2026 21:39:53 +0200 Subject: [PATCH 9/9] MOOOORE TESTS --- epitar/tests/functional/run.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/epitar/tests/functional/run.sh b/epitar/tests/functional/run.sh index 37d5497..70b07f4 100644 --- a/epitar/tests/functional/run.sh +++ b/epitar/tests/functional/run.sh @@ -282,7 +282,23 @@ echo -e "\n$BBlue=== Help and Invalid Arguments ===$Color_Off" test_help "Help flag" test_invalid_args "No arguments" "" test_invalid_args "Invalid flag" "-z" -test_invalid_args "Missing archive" "-c" +test_invalid_args "Invalid flagS" "-zbk" +test_invalid_args "Invalid position" "test.tar -h" +test_invalid_args "Invalid flags and position" "test.tar -z" +test_invalid_args "Invalid flags and position mix" "test.tar -zhx" +test_invalid_args "Invalid position" "test.tar -z" +test_invalid_args "Invalid flag mix" "-zh" +test_invalid_args "Invalid flag mix 2" "-hnh" +test_invalid_args "Invalid flag mix 3" "-cnc" +test_invalid_args "Invalid flag mix 3" "-hhhz" +test_invalid_args "Invalid flag mix 3" "-hnc" +test_invalid_args "Invalid flag mix 3" "-cch" +test_invalid_args "Invalid flag mix 3" "-cnc" +test_invalid_args "Valid flags multiple" "-cc" +test_invalid_args "Incompatible flag" "-cx" +test_invalid_args "Missing op flag" "-v" +test_invalid_args "Missing archive (c)" "-c" +test_invalid_args "Missing archive (x)" "-x" test_invalid_args "Extract with files" "-x archive.tar file.txt" echo -e "\n$BBlue=== Edge Cases ===$Color_Off"