summaryrefslogtreecommitdiffstats
path: root/opkg-opk/opk/write.c
diff options
context:
space:
mode:
Diffstat (limited to 'opkg-opk/opk/write.c')
-rw-r--r--opkg-opk/opk/write.c551
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;
+}