diff options
author | Patrick McDermott <patrick.mcdermott@libiquity.com> | 2023-05-05 21:47:52 (EDT) |
---|---|---|
committer | Patrick McDermott <patrick.mcdermott@libiquity.com> | 2023-05-10 05:41:10 (EDT) |
commit | 19e086419645edd449bde992081b8ca82893ff51 (patch) | |
tree | 70555a89a966c5edd0f46923b4e8551b5166d622 | |
parent | ab74f60d3b8631633ff4164c57fbfb282a1aa7f3 (diff) |
opk/write: Build control and data archives
-rw-r--r-- | configure.ac | 8 | ||||
-rw-r--r-- | src/opk/opk.h | 7 | ||||
-rw-r--r-- | src/opk/write.c | 372 |
3 files changed, 373 insertions, 14 deletions
diff --git a/configure.ac b/configure.ac index 1b6ac90..687629e 100644 --- a/configure.ac +++ b/configure.ac @@ -43,10 +43,10 @@ test -d "${srcdir}/.git" || CFLAGS="${save_CFLAGS}" funcs_missing=false AC_CHECK_FUNCS( [\ - fclose feof ferror fopen fprintf fputs fread free fwrite \ - localtime malloc memcmp memcpy memset mkdir printf puts \ - snprintf sprintf stat strcmp strcpy strftime strlen strncpy \ - strtol + fclose feof ferror fopen fprintf fputs fread free fseek fwrite \ + getgrgid getpwuid localtime lstat malloc memcmp memcpy memset \ + mkdir printf puts readlink scandir snprintf sprintf stat \ + strchr strcmp strcpy strftime strlen strncpy strtol unlink ], [], [funcs_missing=true]) diff --git a/src/opk/opk.h b/src/opk/opk.h index afd39b6..28a6b66 100644 --- a/src/opk/opk.h +++ b/src/opk/opk.h @@ -42,11 +42,18 @@ struct opkg_opk_opk { const char *data_dir; FILE *file; char file_buffer[8192]; + uint64_t mtime; + const char *file_name; + char *outer_uname; + char *outer_gname; struct opkg_opk_gzip *outer_gzip; struct opkg_opk_ustar *outer_ustar; struct opkg_opk_gzip *inner_gzip; struct opkg_opk_ustar *inner_ustar; int previously_printed; + size_t path_len; + char *path; + char *temp_file_name; }; #endif /* OPKG_OPK_OPK_OPK_H_ */ diff --git a/src/opk/write.c b/src/opk/write.c index 9c710f6..2ef052f 100644 --- a/src/opk/write.c +++ b/src/opk/write.c @@ -17,11 +17,15 @@ * along with opkg-opkg. If not, see <http://www.gnu.org/licenses/>. */ +#include <dirent.h> #include <grp.h> #include <pwd.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> +#include <unistd.h> #include "../defs.h" #include "../dirent.h" #include "../gzip.h" @@ -30,6 +34,15 @@ #include "../ustar.h" #include "opk.h" +enum _opkg_opk_opk_archive_type { + OPKG_OPK_OPK_ARCHIVE_TYPE_CONTROL_ = 0, + OPKG_OPK_OPK_ARCHIVE_TYPE_DATA_ = 1, +}; +static const char *OPKG_OPK_OPK_ARCHIVE_NAMES_[] = { + "control.tar.gz", + "data.tar.gz", +}; + static int _opkg_opk_opk_source_date_epoch(uint64_t *mtime) { @@ -47,26 +60,329 @@ _opkg_opk_opk_source_date_epoch(uint64_t *mtime) return OPKG_OPK_OK; } +static int +_opkg_opk_opk_write_filter(const struct dirent *de) +{ + if (de->d_name[0] == '.') { + if (de->d_name[1] == '\0') { + return 0; + } + if (de->d_name[1] == '.' && de->d_name[2] == '\0') { + return 0; + } + } + return 1; +} + +static int +_opkg_opk_opk_write_compar(const struct dirent **a, const struct dirent **b) +{ + return strcmp((*a)->d_name, (*b)->d_name); +} + +/* path_len excludes '\0' */ +static int +_opkg_opk_opk_write_dir_read(struct opkg_opk_opk *opk, + struct opkg_opk_dirent *dir) +{ + int ret; + char *child_path; + int n; + struct dirent **names; + int i; + struct opkg_opk_dirent child; + size_t j; + struct stat stat; + uid_t uid; + char *uname; + gid_t gid; + char *gname; + /* Extra byte (in macro definition) to detect + * overflow, not for NUL */ + char link[OPKG_OPK_USTAR_LINKNAME_SIZE]; + ssize_t link_len; + FILE *fp; + char *buffer; + size_t size; + size_t num_read; + size_t tot_read; + + ret = OPKG_OPK_OK; + + child_path = strchr(opk->path, '\0'); + if (opk->path_len < 2) { + fprintf(stderr, "Error: File name \"%s\" too long", opk->path); + ret = OPKG_OPK_ERROR; + goto out0; + } + *child_path = '/'; + ++child_path; + *child_path = '\0'; + --opk->path_len; + + n = scandir(opk->path, &names, _opkg_opk_opk_write_filter, + _opkg_opk_opk_write_compar); + for (i = 0; i < n; ++i) { + child.name = names[i]->d_name; + child.parent = dir; + for (j = 0; names[i]->d_name[j] != '\0'; ++j) { + if (j == opk->path_len) { + fprintf(stderr, "Error: File name \"%s\" too " + "long", names[i]->d_name); + ret = OPKG_OPK_ERROR; + goto out1; + } + child_path[j] = names[i]->d_name[j]; + } + child_path[j] = '\0'; + lstat(opk->path, &stat); + /* TODO: Ownership override */ + uid = stat.st_uid; + uname = getpwuid(uid)->pw_name; + gid = stat.st_gid; + gname = getgrgid(gid)->gr_name; + if (S_ISDIR(stat.st_mode)) { + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, + stat.st_mode & 0777, + uid, uname, gid, gname, 0, + opk->mtime, 'd', NULL) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + ret = OPKG_OPK_ERROR; + goto out1; + } + if (_opkg_opk_opk_write_dir_read(opk, &child) != + OPKG_OPK_OK) { + ret = OPKG_OPK_ERROR; + goto out1; + } + } else if (S_ISLNK(stat.st_mode)) { + link_len = readlink(opk->path, link, + OPKG_OPK_USTAR_LINKNAME_SIZE); + if (link_len < 0) { + ret = OPKG_OPK_ERROR; + goto out1; + } + if (link_len == OPKG_OPK_USTAR_LINKNAME_SIZE) { + ret = OPKG_OPK_ERROR; + goto out1; + } + link[link_len] = '\0'; + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, 0777, + uid, uname, gid, gname, 0, + opk->mtime, 'l', link) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + ret = OPKG_OPK_ERROR; + goto out1; + } + } else if (S_ISCHR(stat.st_mode)) { + /* TODO */ + } else if (S_ISBLK(stat.st_mode)) { + /* TODO */ + } else if (S_ISFIFO(stat.st_mode)) { + /* TODO */ + } else if (S_ISREG(stat.st_mode)) { + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, + stat.st_mode & 0777, + uid, uname, gid, gname, + stat.st_size, + opk->mtime, '-', NULL) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + ret = OPKG_OPK_ERROR; + goto out1; + } + fp = fopen(opk->path, "rb"); + if (fp == NULL) { + fprintf(stderr, _("Error: Failed to open file " + "\"%s\"\n"), opk->path); + ret = OPKG_OPK_ERROR; + goto out1; + } + opkg_opk_ustar_get_buffer(opk->inner_ustar, + &buffer, &size); + tot_read = 0; + while ((num_read = fread(buffer, 1, size, fp)) > 0) { + opkg_opk_ustar_write_data(opk->inner_ustar, + num_read); + tot_read += num_read; + } + if (ferror(fp) != 0) { + fputs(_("Error: Error reading file\n"), stderr); + fclose(fp); + ret = OPKG_OPK_ERROR; + goto out1; + } + if (fclose(fp) != 0) { + fputs(_("Error: Failed to close file\n"), + stderr); + ret = OPKG_OPK_ERROR; + goto out1; + } + if ((uintmax_t) tot_read != (uintmax_t) stat.st_size) { + fprintf(stderr, _("Error: Expected %jd bytes " + "but read %zu\n"), + (intmax_t) stat.st_size, + num_read); + ret = OPKG_OPK_ERROR; + goto out1; + } + } else { + fprintf(stderr, _("Error: Unknown type of file \"%s\"\n" + ), opk->path); + ret = OPKG_OPK_ERROR; + goto out1; + } + } + + out1: + for (i = 0; i < n; ++i) { + free(names[i]); + } + free(names); + out0: + return ret; +} + +static int +_opkg_opk_opk_build_inner_archive(struct opkg_opk_opk *opk, + enum _opkg_opk_opk_archive_type archive_type) +{ + FILE *fp; + struct opkg_opk_dirent dirent; + size_t written; + char *buffer; + size_t size; + size_t num_read; + size_t tot_read; + + /* Initialize inner gzip compressor. */ + if (sprintf(opk->temp_file_name, "%s~%s", opk->file_name, + OPKG_OPK_OPK_ARCHIVE_NAMES_[archive_type]) <= 0) + { + goto err0; + } + fp = fopen(opk->temp_file_name, "w+b"); + if (fp == NULL) { + fprintf(stderr, _("Error: Failed to open file \"%s\"\n"), + opk->temp_file_name); + goto err0; + } + opk->inner_gzip = opkg_opk_gzip_init_write(fp); + if (opk->inner_gzip == NULL) { + fputs(_("Error: Failed to initialize compressor\n"), stderr); + goto err1; + } + + /* Initialize inner ustar archiver. */ + opk->inner_ustar = opkg_opk_ustar_init(opk->inner_gzip); + if (opk->inner_ustar == NULL) { + fputs(_("Error: Failed to initialize archiver\n"), stderr); + goto err2; + } + + /* Write inner archive to temporary file. */ + dirent.name = "."; + dirent.parent = NULL; + if (archive_type == OPKG_OPK_OPK_ARCHIVE_TYPE_CONTROL_) { + strcpy(opk->path, opk->control_dir); + } else { + strcpy(opk->path, opk->data_dir); + } + if (_opkg_opk_opk_write_dir_read(opk, &dirent) != OPKG_OPK_OK) { + goto err3; + } + + /* Write trailer. */ + if (opkg_opk_ustar_write_trailer(opk->inner_ustar) != OPKG_OPK_OK) { + fputs(_("Error: Failed to write trailer\n"), stderr); + goto err3; + } + + /* Finish inner archive. */ + opkg_opk_ustar_free(opk->inner_ustar); + opkg_opk_gzip_finish_write(opk->inner_gzip); + written = opkg_opk_gzip_written(opk->inner_gzip); + opkg_opk_gzip_free(opk->inner_gzip); + + /* Write header in outer archive. */ + dirent.name = OPKG_OPK_OPK_ARCHIVE_NAMES_[archive_type]; + dirent.parent = NULL; + if (opkg_opk_ustar_write_header(opk->outer_ustar, &dirent, 0644, + 0, opk->outer_uname, 0, opk->outer_gname, + written, opk->mtime, '-', NULL) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), stderr); + goto err1; + } + + /* Read temporary file into outer archive. */ + if (fseek(fp, 0L, SEEK_SET) != 0) { + fputs(_("Error: Failed to seek in file\n"), stderr); + goto err1; + } + opkg_opk_ustar_get_buffer(opk->outer_ustar, &buffer, &size); + tot_read = 0; + while ((num_read = fread(buffer, 1, size, fp)) > 0) { + opkg_opk_ustar_write_data(opk->outer_ustar, num_read); + tot_read += num_read; + } + if (ferror(fp) != 0) { + fputs(_("Error: Error reading file\n"), stderr); + fclose(fp); + goto err1; + } + if (fclose(fp) != 0) { + fputs(_("Error: Failed to close file\n"), stderr); + goto err1; + } + unlink(opk->temp_file_name); + if (tot_read != written) { + fprintf(stderr, _("Error: Wrote %lu bytes but read %lu\n"), + written, num_read); + goto err1; + } + + return OPKG_OPK_OK; + + err3: + opkg_opk_ustar_free(opk->inner_ustar); + err2: + opkg_opk_gzip_free(opk->inner_gzip); + err1: + fclose(fp); + err0: + return OPKG_OPK_ERROR; +} + int opkg_opk_opk_write(struct opkg_opk_opk *opk, const char *file_name) { int ret; - uint64_t mtime; - char *uname; - char *gname; struct opkg_opk_dirent dirent; char *buffer; size_t size; + size_t control_dir_len; + size_t data_dir_len; ret = OPKG_OPK_OK; - if (_opkg_opk_opk_source_date_epoch(&mtime) == OPKG_OPK_ERROR) { + if (_opkg_opk_opk_source_date_epoch(&opk->mtime) == OPKG_OPK_ERROR) { fputs(_("Error: Missing or invalid SOURCE_DATE_EPOCH\n"), stderr); ret = OPKG_OPK_ERROR; goto out0; } + opk->file_name = file_name; + /* Open outer archive. */ opk->file = fopen(file_name, "wb"); if (opk->file == NULL) { @@ -92,15 +408,15 @@ opkg_opk_opk_write(struct opkg_opk_opk *opk, const char *file_name) goto out2; } - uname = getpwuid(0)->pw_name; - gname = getgrgid(0)->gr_name; + opk->outer_uname = getpwuid(0)->pw_name; + opk->outer_gname = getgrgid(0)->gr_name; /* Write version file. */ dirent.name = "debian-binary"; dirent.parent = NULL; if (opkg_opk_ustar_write_header(opk->outer_ustar, &dirent, 0644, - 0, uname, 0, gname, 4, mtime, '-', NULL) != - OPKG_OPK_OK) { + 0, opk->outer_uname, 0, opk->outer_gname, + 4, opk->mtime, '-', NULL) != OPKG_OPK_OK) { fputs(_("Error: Failed to write header\n"), stderr); ret = OPKG_OPK_ERROR; goto out3; @@ -113,15 +429,51 @@ opkg_opk_opk_write(struct opkg_opk_opk *opk, const char *file_name) goto out3; } - /* TODO: control.tar.gz and data.tar.gz */ + /* Allocate control and data file path buffer. */ + opk->path_len = control_dir_len = strlen(opk->control_dir); + data_dir_len = strlen(opk->data_dir); + if (data_dir_len > opk->path_len) { + opk->path_len = data_dir_len; + } + opk->path_len += 257; + opk->path = malloc(opk->path_len); + if (opk->path == NULL) { + ret = OPKG_OPK_ERROR; + goto out3; + } + + /* Allocate temporary archive file name buffer. */ + opk->temp_file_name = + malloc(sizeof(file_name) + sizeof("~control.tar.gz")); + if (opk->temp_file_name == NULL) { + ret = OPKG_OPK_ERROR; + goto out4; + } + + if (_opkg_opk_opk_build_inner_archive(opk, + OPKG_OPK_OPK_ARCHIVE_TYPE_CONTROL_) != + OPKG_OPK_OK) { + ret = OPKG_OPK_ERROR; + goto out4; + } + if (_opkg_opk_opk_build_inner_archive(opk, + OPKG_OPK_OPK_ARCHIVE_TYPE_DATA_) != + OPKG_OPK_OK) { + ret = OPKG_OPK_ERROR; + goto out4; + } /* Write trailer. */ if (opkg_opk_ustar_write_trailer(opk->outer_ustar) != OPKG_OPK_OK) { fputs(_("Error: Failed to write trailer\n"), stderr); ret = OPKG_OPK_ERROR; - goto out3; + goto out5; } + out5: + free(opk->temp_file_name); + out4: + free(opk->path); out3: opkg_opk_ustar_free(opk->outer_ustar); out2: |