diff --git a/epitar/src/main.c b/epitar/src/main.c index 1184ebf..fb8d193 100644 --- a/epitar/src/main.c +++ b/epitar/src/main.c @@ -1,13 +1,13 @@ -#include "archive.h" -#include "config.h" -#include "extract.h" +#include "tar/archive.h" +#include "tar/extract.h" +#include "utils/config.h" int main(int argc, char **argv) { // Handle args struct config config; - enum arg_error_code err = args_handler(argc, argv, &config); - switch (err) + enum arg_error_code arg_err = args_handler(argc, argv, &config); + switch (arg_err) { case ARG_VALID: break; @@ -21,14 +21,32 @@ int main(int argc, char **argv) return 0; } + enum error_code err; if (config.mode == EXTRACT) { - extract(config.archive_file, config.verbose); + err = extract(config.archive_file, config.verbose); } else if (config.mode == ARCHIVE) { - archive(config.archive_file, config.input_files, config.verbose); + err = archive(config.archive_file, config.input_files, config.verbose); } - return 0; + switch (err) + { + case SUCCESS: + return 0; + + case FILE_NOT_FOUND: + case UNKNOWN_ERROR: + puts("epitar: error extracting tarball "); + return 3; + + case BAD_CHECKSUM: + puts("epitar: bad checksum"); + return 2; + + default: + puts("epitar: error extracting tarball "); + return 3; + } } diff --git a/epitar/src/tar/archive.c b/epitar/src/tar/archive.c new file mode 100644 index 0000000..278ab31 --- /dev/null +++ b/epitar/src/tar/archive.c @@ -0,0 +1,6 @@ +#include "archive.h" +#include "tar.h" + +enum error_code archive(char *archive_name, char **files, bool verbose) { + return UNKNOWN_ERROR; +} diff --git a/epitar/src/archive.h b/epitar/src/tar/archive.h similarity index 75% rename from epitar/src/archive.h rename to epitar/src/tar/archive.h index 4556fa7..52c1e89 100644 --- a/epitar/src/archive.h +++ b/epitar/src/tar/archive.h @@ -8,6 +8,6 @@ /* @brief Archives the listed `files` into `archive_name` * @return 0 on success, the corresponding error code otherwise */ -int archive(char *archive_name, char **files, bool verbose); +enum error_code archive(char *archive_name, char **files, bool verbose); #endif // ARCHIVE_H diff --git a/epitar/src/tar/extract.c b/epitar/src/tar/extract.c new file mode 100644 index 0000000..c617aba --- /dev/null +++ b/epitar/src/tar/extract.c @@ -0,0 +1,147 @@ +#include "extract.h" + +#include +#include +#include + +#include "../utils/filesystem.h" + +/** + * + * @arg stream Pointer to the file's content + * @arg header Pointer to the file header + */ +static enum error_code extract_ustar(FILE *stream, struct ustar_header *header) +{ + // Length + size_t file_length = atol(header->size); + if (file_length == 0) + return INVALID_FORMAT; + + struct file_stats stats = { .name = header->name, + .length = atol(header->size), + .content_stream = stream, + .mode = header->mode, + .uid = header->uid, + .gid = header->gid, + .mtime = header->mtime }; + // Type + switch (header->typeflag) + { + // Regular file + case REGTYPE: + case AREGTYPE: + return create_file(&stats); + + // Link + case LNKTYPE: + return create_link(&stats, header->linkname); + + // Directory + case DIRTYPE: + return create_directory(&stats); + + default: + return NOT_IMPLEMENTED; + } +} + +/** + * + * @arg stream Pointer to the file's content + * @arg header Pointer to the file header + */ +static enum error_code extract_unixtar(FILE *stream, + struct unixtar_header *header) +{ + (void)stream; + (void)header; + return NOT_IMPLEMENTED; +} + +/** + * @brief Extracts a single file from the stream + * @arg content Pointer to the file's content + * @arg header Pointer to the file header + */ +static enum error_code extract_file(FILE *content, union tar_header *header, + bool verbose) +{ + struct ustar_header *ustar_header; + struct unixtar_header *unixtar_header; + + // USTAR + if ((ustar_header = get_ustar_header(header)) != NULL) + { + if (verbose) + printf("./%s", 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); + return extract_unixtar(content, unixtar_header); + } + // Unsupported format + else + { + return INVALID_FORMAT; + } +} + +enum error_code extract(char *archive_name, bool verbose) +{ + FILE *stream = fopen(archive_name, "r"); + if (stream == NULL) + return FILE_NOT_FOUND; + + // // Check size + // fseek(file, 0L, SEEK_END); + // long size = ftell(file); + // if (size < BLOCK_SIZE) { + // fclose(file); + // return INVALID_FORMAT; + // } + // // Go back + // fseek(file, 0L, SEEK_SET); + + enum error_code read_state = PENDING; + bool got_empty_block = false; + + while (read_state == PENDING) + { + union tar_header header; + size_t bytes_read = fread(&header, 1, sizeof(union tar_header), stream); + if (bytes_read < sizeof(union tar_header)) + { + read_state = INVALID_FORMAT; // Unexpected EOF + } + + // Check if empty + char empty_block[BLOCK_SIZE] = { 0 }; + if (memcmp(&header.raw_block, empty_block, BLOCK_SIZE) != 0) + { + if (got_empty_block) // Previous block empty + read_state = SUCCESS; + else + got_empty_block = true; + continue; + } + else + { + got_empty_block = false; + } + + // Extract file + enum error_code err = extract_file(stream, &header, verbose); + if (err != SUCCESS) + { + fclose(stream); + return err; + } + } + + return UNKNOWN_ERROR; +} diff --git a/epitar/src/extract.h b/epitar/src/tar/extract.h similarity index 71% rename from epitar/src/extract.h rename to epitar/src/tar/extract.h index de7f903..fff3c0f 100644 --- a/epitar/src/extract.h +++ b/epitar/src/tar/extract.h @@ -1,13 +1,14 @@ #ifndef EXTRACT_H #define EXTRACT_H -#include "tar.h" - #include +#include "../utils/errors.h" +#include "tar.h" + /* @brief Extract and write files from `archive_name` * @return 0 on success, the corresponding error code otherwise */ -int extract(char *archive_name, bool verbose); +enum error_code extract(char *archive_name, bool verbose); #endif // EXTRACT_H diff --git a/epitar/src/tar.c b/epitar/src/tar/tar.c similarity index 100% rename from epitar/src/tar.c rename to epitar/src/tar/tar.c diff --git a/epitar/src/tar.h b/epitar/src/tar/tar.h similarity index 95% rename from epitar/src/tar.h rename to epitar/src/tar/tar.h index 5ac0be5..ebf660c 100644 --- a/epitar/src/tar.h +++ b/epitar/src/tar/tar.h @@ -42,14 +42,6 @@ // === Structures -enum error_codes -{ - SUCCESS = 0, - UNKNOWN_ERROR, - FILE_NOT_FOUND, - BAD_CHECKSUM, -}; - struct ustar_header { char name[100]; @@ -88,7 +80,7 @@ union tar_header { struct unixtar_header unixtar; struct ustar_header ustar; - char raw_block[512]; + char raw_block[BLOCK_SIZE]; }; // === Functions diff --git a/epitar/src/utils.h b/epitar/src/utils.h deleted file mode 100644 index 8bc97b2..0000000 --- a/epitar/src/utils.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H - -void print_str_array(const char **input_files); - -#endif /* UTILS_H */ diff --git a/epitar/src/config.c b/epitar/src/utils/config.c similarity index 100% rename from epitar/src/config.c rename to epitar/src/utils/config.c diff --git a/epitar/src/config.h b/epitar/src/utils/config.h similarity index 100% rename from epitar/src/config.h rename to epitar/src/utils/config.h diff --git a/epitar/src/utils/errors.h b/epitar/src/utils/errors.h new file mode 100644 index 0000000..eaf81ea --- /dev/null +++ b/epitar/src/utils/errors.h @@ -0,0 +1,17 @@ +#ifndef ERRORS_H +#define ERRORS_H + +enum error_code +{ + SUCCESS = 0, + PENDING, + UNKNOWN_ERROR, + FILE_NOT_FOUND, + BAD_CHECKSUM, + INVALID_FORMAT, + CANNOT_CREATE_TARGET, + TARGET_ALREADY_EXISTS, + NOT_IMPLEMENTED +}; + +#endif // ERRORS_H diff --git a/epitar/src/utils/filesystem.c b/epitar/src/utils/filesystem.c new file mode 100644 index 0000000..3c4089d --- /dev/null +++ b/epitar/src/utils/filesystem.c @@ -0,0 +1,155 @@ +#include "filesystem.h" +#include "errors.h" + +#include +#include +#include +#include +#include +#include +#include + +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) + return UNKNOWN_ERROR; + // Create dir + struct stat st = { 0 }; + if (stat(stats->name, &st) == -1) + { + mode_t mode_val = strtol(stats->mode, NULL, 8); + int err = mkdir(stats->name, mode_val); + if (err != 0) + return CANNOT_CREATE_TARGET; + } + else + { + return TARGET_ALREADY_EXISTS; + } + + // 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; + return SUCCESS; +} + +enum error_code create_link(struct file_stats *stats, char *target) +{ + if (stats == NULL) + return UNKNOWN_ERROR; + + // Create link + int err = symlink(target, stats->name); + if (err != 0) + 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; + // 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) + // return errc; + + return SUCCESS; +} + +enum error_code create_file(struct file_stats *stats) +{ + // Turbo moche mais allez vous plaindre au clang format + // Check fields + if (stats == NULL || stats->name == NULL || stats->length == 0 + || stats->content_stream == NULL || stats->mode == NULL + || stats->uid == NULL || stats->gid == NULL || stats->mtime == NULL) + { + return UNKNOWN_ERROR; + } + + // Create and open file + FILE *file_stream = fopen(stats->name, "w+"); + 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; + } + + // Close file + 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; + + return SUCCESS; +} + +enum error_code setowner(char *path, char *uid, char *gid) +{ + if (path == NULL || uid == NULL || gid == NULL) + return UNKNOWN_ERROR; + + uid_t uid_val = (uid_t)strtol(uid, NULL, 10); + gid_t gid_val = (gid_t)strtol(gid, NULL, 10); + + int err = chown(path, uid_val, gid_val); + return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; +} + +enum error_code setmtime(char *path, char *mtime) +{ + if (path == NULL || mtime == NULL) + return UNKNOWN_ERROR; + + // Parse octal mtime string from tar header + time_t time_val = strtol(mtime, NULL, 8); + if (time_val == 0 && mtime[0] != '0') + return UNKNOWN_ERROR; + + struct utimbuf utb; + utb.actime = time_val; + utb.modtime = time_val; + + int err = utime(path, &utb); + return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; +} + +enum error_code setmode(char *path, char *mode) +{ + if (path == NULL || mode == NULL) + return UNKNOWN_ERROR; + + // Parse octal mode string from tar header + mode_t mode_val = strtol(mode, NULL, 8); + if (mode_val == 0 && mode[0] != '0') + return UNKNOWN_ERROR; + + int err = chmod(path, mode_val); + return err == 0 ? SUCCESS : CANNOT_CREATE_TARGET; +} diff --git a/epitar/src/utils/filesystem.h b/epitar/src/utils/filesystem.h new file mode 100644 index 0000000..6b16787 --- /dev/null +++ b/epitar/src/utils/filesystem.h @@ -0,0 +1,28 @@ +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include + +#include "errors.h" + +struct file_stats +{ + char *name; + size_t length; + FILE *content_stream; + char *mode; + char *uid; + char *gid; + char *mtime; +}; + +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); + +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); + +#endif // FILESYSTEM_H diff --git a/epitar/src/utils.c b/epitar/src/utils/misc.c similarity index 100% rename from epitar/src/utils.c rename to epitar/src/utils/misc.c diff --git a/epitar/src/utils/misc.h b/epitar/src/utils/misc.h new file mode 100644 index 0000000..3d20ecc --- /dev/null +++ b/epitar/src/utils/misc.h @@ -0,0 +1,6 @@ +#ifndef MISC_H +#define MISC_H + +void print_str_array(const char **input_files); + +#endif /* MISC_H */