mirror of
https://github.com/pygos/pkg-utils.git
synced 2024-12-27 15:10:51 +01:00
352 lines
6.6 KiB
C
352 lines
6.6 KiB
C
/* SPDX-License-Identifier: ISC */
|
|
#include "pack.h"
|
|
|
|
static char *skipspace(char *str)
|
|
{
|
|
while (isspace(*str))
|
|
++str;
|
|
return str;
|
|
}
|
|
|
|
static void oom(const char *filename, size_t linenum)
|
|
{
|
|
input_file_complain(filename, linenum, "out of memory");
|
|
}
|
|
|
|
static void unescape_name(char *name)
|
|
{
|
|
char *dst = name, *src = name + 1;
|
|
|
|
while (*src != '"' && *src != '\0') {
|
|
if (src[0] == '\\' && (src[1] == '\\' || src[1] == '"')) {
|
|
*(dst++) = src[1];
|
|
src += 2;
|
|
} else {
|
|
*(dst++) = *(src++);
|
|
}
|
|
}
|
|
|
|
*dst = '\0';
|
|
}
|
|
|
|
static image_entry_t *filelist_mkentry(char *line, const char *filename,
|
|
size_t linenum, mode_t filetype)
|
|
{
|
|
image_entry_t *ent;
|
|
char *start = line;
|
|
size_t i;
|
|
|
|
ent = calloc(1, sizeof(*ent));
|
|
if (ent == NULL) {
|
|
oom(filename, linenum);
|
|
return NULL;
|
|
}
|
|
|
|
/* name */
|
|
if (*line == '"') {
|
|
for (i = 1; line[i] != '"' && line[i] != '\0'; ++i) {
|
|
if (line[i] == '\\' &&
|
|
(line[i + 1] == '\\' || line[i + 1] == '"')) {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
if (line[i++] != '"') {
|
|
input_file_complain(filename, linenum,
|
|
"missing \" after file name");
|
|
goto fail;
|
|
}
|
|
} else {
|
|
for (i = 0; !isspace(line[i]) && line[i] != '\0'; ++i)
|
|
;
|
|
}
|
|
|
|
if (!isspace(line[i])) {
|
|
input_file_complain(filename, linenum,
|
|
"expected space after file name");
|
|
goto fail;
|
|
}
|
|
|
|
ent->name = calloc(1, i + 1);
|
|
if (ent->name == NULL) {
|
|
oom(filename, linenum);
|
|
goto fail;
|
|
}
|
|
|
|
memcpy(ent->name, line, i);
|
|
|
|
if (ent->name[0] == '"')
|
|
unescape_name(ent->name);
|
|
|
|
if (canonicalize_name(ent->name)) {
|
|
input_file_complain(filename, linenum, "invalid file name");
|
|
goto fail;
|
|
}
|
|
|
|
if (ent->name[0] == '\0') {
|
|
input_file_complain(filename, linenum,
|
|
"refusing to add entry for '/'");
|
|
goto fail;
|
|
}
|
|
|
|
if (strlen(ent->name) > 0xFFFF) {
|
|
input_file_complain(filename, linenum, "name too long");
|
|
goto fail;
|
|
}
|
|
|
|
line = skipspace(line + i);
|
|
|
|
/* mode */
|
|
if (!isdigit(*line)) {
|
|
input_file_complain(filename, linenum,
|
|
"expected numeric mode after file name");
|
|
goto fail;
|
|
}
|
|
|
|
while (isdigit(*line)) {
|
|
if (*line > '7') {
|
|
input_file_complain(filename, linenum,
|
|
"mode must be octal number");
|
|
goto fail;
|
|
}
|
|
ent->mode = (ent->mode << 3) | (*(line++) - '0');
|
|
}
|
|
|
|
if (!isspace(*line)) {
|
|
input_file_complain(filename, linenum,
|
|
"expected space after file mode");
|
|
goto fail;
|
|
}
|
|
|
|
line = skipspace(line);
|
|
|
|
ent->mode = (ent->mode & ~S_IFMT) | filetype;
|
|
|
|
/* uid */
|
|
if (!isdigit(*line)) {
|
|
input_file_complain(filename, linenum,
|
|
"expected numeric UID after mode");
|
|
goto fail;
|
|
}
|
|
|
|
while (isdigit(*line))
|
|
ent->uid = (ent->uid * 10) + (*(line++) - '0');
|
|
|
|
if (!isspace(*line)) {
|
|
input_file_complain(filename, linenum,
|
|
"expected space after UID");
|
|
goto fail;
|
|
}
|
|
|
|
line = skipspace(line);
|
|
|
|
/* gid */
|
|
if (!isdigit(*line)) {
|
|
input_file_complain(filename, linenum,
|
|
"expected numeric GID after UID");
|
|
goto fail;
|
|
}
|
|
|
|
while (isdigit(*line))
|
|
ent->gid = (ent->gid * 10) + (*(line++) - '0');
|
|
|
|
line = skipspace(line);
|
|
|
|
/* remove processed data */
|
|
memmove(start, line, strlen(line) + 1);
|
|
return ent;
|
|
fail:
|
|
free(ent->name);
|
|
free(ent);
|
|
return NULL;
|
|
}
|
|
|
|
int filelist_mkdir(char *line, const char *filename,
|
|
size_t linenum, void *obj)
|
|
{
|
|
image_entry_t *ent = filelist_mkentry(line,filename,linenum,S_IFDIR);
|
|
image_entry_t **listptr = obj;
|
|
|
|
if (ent == NULL)
|
|
return -1;
|
|
|
|
ent->next = *listptr;
|
|
*listptr = ent;
|
|
return 0;
|
|
}
|
|
|
|
int filelist_mkslink(char *line, const char *filename,
|
|
size_t linenum, void *obj)
|
|
{
|
|
image_entry_t *ent = filelist_mkentry(line,filename,linenum,S_IFLNK);
|
|
image_entry_t **listptr = obj;
|
|
|
|
if (ent == NULL)
|
|
return -1;
|
|
|
|
ent->data.symlink.target = strdup(line);
|
|
if (ent->data.symlink.target == NULL) {
|
|
oom(filename, linenum);
|
|
goto fail;
|
|
}
|
|
|
|
if (strlen(ent->data.symlink.target) > 0xFFFF) {
|
|
input_file_complain(filename, linenum,
|
|
"symlink target too long");
|
|
goto fail;
|
|
}
|
|
|
|
ent->next = *listptr;
|
|
*listptr = ent;
|
|
return 0;
|
|
fail:
|
|
image_entry_free(ent);
|
|
return -1;
|
|
}
|
|
|
|
int filelist_mkfile(char *line, const char *filename,
|
|
size_t linenum, void *obj)
|
|
{
|
|
image_entry_t *ent = filelist_mkentry(line,filename,linenum,S_IFREG);
|
|
image_entry_t **listptr = obj;
|
|
const char *ptr;
|
|
struct stat sb;
|
|
|
|
if (ent == NULL)
|
|
return -1;
|
|
|
|
if (line[0] == '\0') {
|
|
ptr = strrchr(filename, '/');
|
|
|
|
if (ptr == NULL) {
|
|
ent->data.file.location = strdup(ent->name);
|
|
} else {
|
|
asprintf(&ent->data.file.location, "%.*s/%s",
|
|
(int)(ptr - filename), filename,
|
|
ent->name);
|
|
}
|
|
} else {
|
|
ent->data.file.location = strdup(line);
|
|
}
|
|
|
|
if (ent->data.file.location == NULL) {
|
|
oom(filename, linenum);
|
|
goto fail;
|
|
}
|
|
|
|
if (stat(ent->data.file.location, &sb) != 0) {
|
|
perror(ent->data.file.location);
|
|
goto fail;
|
|
}
|
|
|
|
if (sizeof(off_t) > sizeof(uint64_t) &&
|
|
sb.st_size > (off_t)(~((uint64_t)0))) {
|
|
input_file_complain(filename, linenum,
|
|
"input file is too big");
|
|
goto fail;
|
|
}
|
|
|
|
ent->data.file.size = sb.st_size;
|
|
|
|
ent->next = *listptr;
|
|
*listptr = ent;
|
|
return 0;
|
|
fail:
|
|
image_entry_free(ent);
|
|
return -1;
|
|
}
|
|
|
|
static int filelist_mknod(char *line, const char *filename,
|
|
size_t linenum, void *obj)
|
|
{
|
|
image_entry_t **listptr = obj, *ent;
|
|
unsigned int maj, min;
|
|
char *ptr;
|
|
|
|
ent = filelist_mkentry(line, filename, linenum, S_IFCHR);
|
|
if (ent == NULL)
|
|
return -1;
|
|
|
|
switch (line[0]) {
|
|
case 'c':
|
|
case 'C':
|
|
break;
|
|
case 'b':
|
|
case 'B':
|
|
ent->mode = (ent->mode & (~S_IFMT)) | S_IFBLK;
|
|
break;
|
|
default:
|
|
goto fail;
|
|
}
|
|
|
|
if (!isspace(line[1]))
|
|
goto fail;
|
|
|
|
ptr = line + 1;
|
|
while (isspace(*ptr))
|
|
++ptr;
|
|
|
|
if (sscanf(ptr, "%u %u", &maj, &min) != 2)
|
|
goto fail;
|
|
|
|
ent->data.device.devno = makedev(maj, min);
|
|
|
|
ent->next = *listptr;
|
|
*listptr = ent;
|
|
return 0;
|
|
fail:
|
|
image_entry_free(ent);
|
|
input_file_complain(filename, linenum,
|
|
"error in device specification");
|
|
return -1;
|
|
}
|
|
|
|
static const keyword_handler_t line_hooks[] = {
|
|
{ "file", filelist_mkfile },
|
|
{ "dir", filelist_mkdir },
|
|
{ "slink", filelist_mkslink },
|
|
{ "nod", filelist_mknod },
|
|
};
|
|
|
|
#define NUM_LINE_HOOKS (sizeof(line_hooks) / sizeof(line_hooks[0]))
|
|
|
|
static int alloc_file_ids(image_entry_t *list)
|
|
{
|
|
image_entry_t *ent;
|
|
uint64_t file_id = 0;
|
|
|
|
for (ent = list; ent != NULL; ent = ent->next) {
|
|
if (!S_ISREG(ent->mode))
|
|
continue;
|
|
|
|
if (file_id > 0x00000000FFFFFFFFUL) {
|
|
fprintf(stderr, "too many input files\n");
|
|
return -1;
|
|
}
|
|
|
|
ent->data.file.id = file_id++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int filelist_read(const char *filename, image_entry_t **out)
|
|
{
|
|
image_entry_t *list = NULL;
|
|
|
|
if (process_file(filename, line_hooks, NUM_LINE_HOOKS, &list))
|
|
goto fail;
|
|
|
|
if (list != NULL) {
|
|
list = image_entry_sort(list);
|
|
|
|
if (alloc_file_ids(list))
|
|
goto fail;
|
|
}
|
|
|
|
*out = list;
|
|
return 0;
|
|
fail:
|
|
image_entry_free_list(list);
|
|
return -1;
|
|
}
|