/*
* 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 .
*/
#include
#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 "../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 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_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;
}