/* * Copyright (C) 2023 Patrick McDermott * * This file is part of opkg-opkg. * * opkg-opkg 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-opkg 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-opkg. If not, see . */ #include #include #include #include #include #include #include #include #include #include "../defs.h" #include "../dirent.h" #include "../gzip.h" #include "../i18n.h" #include "../opk.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); } /* 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; 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(&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, 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. */ 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 out5; } out5: free(opk->temp_file_name); out4: free(opk->path); 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; }