/*
* 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
#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 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;
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';
if (lstat(opk->path, &st) != 0) {
fprintf(stderr, _("Error: Failed to stat \"%s\"\n"),
opk->path);
ret = OPKG_OPK_ERROR;
goto out1;
}
/* 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 (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);
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(st.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, 0, 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(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);
ret = OPKG_OPK_ERROR;
goto out1;
}
} 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);
ret = OPKG_OPK_ERROR;
goto out1;
}
} 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);
ret = OPKG_OPK_ERROR;
goto out1;
}
} else if (S_ISREG(st.st_mode)) {
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);
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) st.st_size) {
fprintf(stderr, _("Error: Expected %jd bytes "
"but read %zu\n"),
(intmax_t) st.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;
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_) {
strcpy(opk->path, opk->control_dir);
} else {
strcpy(opk->path, opk->data_dir);
}
if (stat(opk->path, &st) != 0) {
fprintf(stderr, _("Error: Failed to stat \"%s\"\n"), opk->path);
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) != 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 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, 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. */
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;
}