diff options
Diffstat (limited to 'opkg-opk/opk/write.c')
-rw-r--r-- | opkg-opk/opk/write.c | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/opkg-opk/opk/write.c b/opkg-opk/opk/write.c new file mode 100644 index 0000000..284b625 --- /dev/null +++ b/opkg-opk/opk/write.c @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2023 Patrick McDermott + * + * This file is part of opkg-opk. + * + * opkg-opk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opkg-opk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opkg-opk. If not, see <https://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 <sys/sysmacros.h> +#include <unistd.h> +#include "../defs.h" +#include "../dirent.h" +#include "../gzip.h" +#include "../i18n.h" +#include "../opk.h" +#include "../specials.h" +#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) +{ + char *env; + char *end; + + env = getenv("SOURCE_DATE_EPOCH"); + if (env == NULL) { + return OPKG_OPK_ERROR; + } + *mtime = strtol(env, &end, 10); + if (*end != '\0') { + return OPKG_OPK_ERROR; + } + 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); +} + +static int +_opkg_opk_opk_write_dir_read(struct opkg_opk_opk *opk, + struct opkg_opk_dirent *dir, size_t path_off) +{ + char *child_path; + int children_n; + struct dirent **children; + int children_i; + struct opkg_opk_dirent child; + size_t name_i; + struct stat st; + 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; + char type; + uint32_t devmajor; + uint32_t devminor; + FILE *fp; + char *buffer; + size_t size; + size_t num_read; + size_t tot_read; + int ret; + + child_path = opk->path_virt + path_off; + children_n = scandir(opk->path_real, &children, _opkg_opk_opk_write_filter, + _opkg_opk_opk_write_compar); + for (children_i = 0; children_i < children_n; ++children_i) { + child.name = children[children_i]->d_name; + child.parent = dir; + for (name_i = 0; children[children_i]->d_name[name_i] != '\0'; + ++name_i) { + if (path_off + name_i + 1 == OPKG_OPK_USTAR_NAME_SIZE) { + fprintf(stderr, "Error: File name \"%s\" too " + "long", + children[children_i]->d_name); + goto err; + } + child_path[name_i] = + children[children_i]->d_name[name_i]; + } + child_path[name_i] = '\0'; + if (lstat(opk->path_real, &st) != 0) { + fprintf(stderr, _("Error: Failed to stat \"%s\"\n"), + opk->path_real); + goto err; + } + /* TODO: Ownership override */ + uid = st.st_uid; + uname = getpwuid(uid)->pw_name; + gid = st.st_gid; + gname = getgrgid(gid)->gr_name; + if (S_ISDIR(st.st_mode)) { + if (path_off + name_i + 2 == OPKG_OPK_USTAR_NAME_SIZE) { + fprintf(stderr, "Error: File name \"%s\" too " + "long", child_path); + goto err; + } + child_path[name_i] = '/'; + child_path[++name_i] = '\0'; + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, st.st_mode & 0777, + uid, uname, gid, gname, 0, 0, 0, + opk->mtime, 'd', NULL) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + goto err; + } + if (_opkg_opk_opk_write_dir_read(opk, &child, + path_off + name_i) != + OPKG_OPK_OK) { + goto err; + } + } else if (S_ISLNK(st.st_mode)) { + link_len = readlink(opk->path_real, link, + OPKG_OPK_USTAR_LINKNAME_SIZE); + if (link_len < 0) { + goto err; + } + if (link_len == OPKG_OPK_USTAR_LINKNAME_SIZE) { + goto err; + } + link[link_len] = '\0'; + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, 0777, + uid, uname, gid, gname, 0, 0, 0, + opk->mtime, 'l', link) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + goto err; + } + } else if (S_ISCHR(st.st_mode)) { + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, st.st_mode & 0777, + uid, uname, gid, gname, + major(st.st_rdev), + minor(st.st_rdev), + 0, opk->mtime, 'c', NULL) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + goto err; + } + } else if (S_ISBLK(st.st_mode)) { + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, st.st_mode & 0777, + uid, uname, gid, gname, + major(st.st_rdev), + minor(st.st_rdev), + 0, opk->mtime, 'b', NULL) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + goto err; + } + } else if (S_ISFIFO(st.st_mode)) { + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, st.st_mode & 0777, + uid, uname, gid, gname, 0, 0, 0, + opk->mtime, 'p', NULL) != + OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + goto err; + } + } else if (S_ISREG(st.st_mode)) { + if (opkg_opk_specials_find(opk->specials, + opk->path_virt, &type, + &devmajor, &devminor) == + OPKG_OPK_OK) { + if (opkg_opk_ustar_write_header( + opk->inner_ustar, + &child, + st.st_mode & 0777, + uid, uname, gid, gname, + devmajor, devminor, 0, + opk->mtime, type, NULL) + != OPKG_OPK_OK) { + fputs(_("Error: Failed to write header" + "\n"), stderr); + goto err; + } + continue; + } + if (opkg_opk_ustar_write_header(opk->inner_ustar, + &child, st.st_mode & 0777, + uid, uname, gid, gname, 0, 0, + st.st_size, opk->mtime, '-', + NULL) != OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + goto err; + } + fp = fopen(opk->path_real, "rb"); + if (fp == NULL) { + fprintf(stderr, _("Error: Failed to open file " + "\"%s\"\n"), + opk->path_real); + goto err; + } + 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); + goto err; + } + if (fclose(fp) != 0) { + fputs(_("Error: Failed to close file\n"), + stderr); + goto err; + } + if ((uintmax_t) tot_read != (uintmax_t) st.st_size) { + fprintf(stderr, _("Error: Expected %jd bytes " + "but read %zu\n"), + (intmax_t) st.st_size, + num_read); + goto err; + } + } else { + fprintf(stderr, _("Error: Unknown type of file \"%s\"\n" + ), opk->path_real); + goto err; + } + } + + ret = OPKG_OPK_OK; + goto out; + err: + ret = OPKG_OPK_ERROR; + out: + for (children_i = 0; children_i < children_n; ++children_i) { + free(children[children_i]); + } + free(children); + 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; + struct stat st; + uid_t uid; + char *uname; + gid_t gid; + char *gname; + 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_) { + sprintf(opk->path_real, "%s/", opk->control_dir); + opk->path_virt = opk->path_real + opk->control_dir_len; + } else { + sprintf(opk->path_real, "%s/", opk->data_dir); + opk->path_virt = opk->path_real + opk->data_dir_len; + } + if (stat(opk->path_real, &st) != 0) { + fprintf(stderr, _("Error: Failed to stat \"%s\"\n"), + opk->path_real); + goto err3; + } + uid = st.st_uid; + uname = getpwuid(uid)->pw_name; + gid = st.st_gid; + gname = getgrgid(gid)->gr_name; + if (opkg_opk_ustar_write_header(opk->inner_ustar, &dirent, + st.st_mode & 0777, uid, uname, gid, gname, 0, + 0, 0, opk->mtime, 'd', NULL) != OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), + stderr); + goto err3; + } + if (_opkg_opk_opk_write_dir_read(opk, &dirent, 1 /* Skip '/' */) != + 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, 0, 0, + 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; + struct opkg_opk_dirent dirent; + char *buffer; + size_t size; + size_t path_len; + + ret = OPKG_OPK_OK; + + 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) { + fprintf(stderr, _("Error: Failed to open file \"%s\"\n"), + file_name); + ret = OPKG_OPK_ERROR; + goto out0; + } + + /* Initialize outer gzip compressor. */ + opk->outer_gzip = opkg_opk_gzip_init_write(opk->file); + if (opk->outer_gzip == NULL) { + fputs(_("Error: Failed to initialize\n"), stderr); + ret = OPKG_OPK_ERROR; + goto out1; + } + + /* Initialize outer ustar archiver. */ + opk->outer_ustar = opkg_opk_ustar_init(opk->outer_gzip); + if (opk->outer_ustar == NULL) { + fputs(_("Error: Failed to initialize\n"), stderr); + ret = OPKG_OPK_ERROR; + goto out2; + } + + 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, opk->outer_uname, 0, opk->outer_gname, 0, 0, + 4, opk->mtime, '-', NULL) != OPKG_OPK_OK) { + fputs(_("Error: Failed to write header\n"), stderr); + ret = OPKG_OPK_ERROR; + goto out3; + } + opkg_opk_ustar_get_buffer(opk->outer_ustar, &buffer, &size); + memcpy(buffer, "2.0\n", 4); + if (opkg_opk_ustar_write_data(opk->outer_ustar, 4) != OPKG_OPK_OK) { + fputs(_("Error: Failed to write data\n"), stderr); + ret = OPKG_OPK_ERROR; + goto out3; + } + + /* Allocate control and data file path buffer. */ + if (opk->control_dir_len >= opk->data_dir_len) { + path_len = opk->control_dir_len; + } else { + path_len = opk->data_dir_len; + } + path_len += 1 /* '/' */ + OPKG_OPK_USTAR_NAME_SIZE; + opk->path_real = malloc(path_len); + if (opk->path_real == NULL) { + ret = OPKG_OPK_ERROR; + goto out3; + } + + /* Allocate temporary archive file name buffer. */ + opk->temp_file_name = + malloc(strlen(file_name) + strlen("~control.tar.gz") + 1); + 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 out5; + } + if (_opkg_opk_opk_build_inner_archive(opk, + OPKG_OPK_OPK_ARCHIVE_TYPE_DATA_) != + OPKG_OPK_OK) { + ret = OPKG_OPK_ERROR; + goto out5; + } + + /* 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 out5; + } + + out5: + free(opk->temp_file_name); + out4: + free(opk->path_real); + out3: + opkg_opk_ustar_free(opk->outer_ustar); + out2: + opkg_opk_gzip_finish_write(opk->outer_gzip); + opkg_opk_gzip_free(opk->outer_gzip); + out1: + fclose(opk->file); + out0: + return ret; +} |