Skip to content

Instantly share code, notes, and snippets.

@iamahuman
Created April 4, 2021 08:15
Show Gist options
  • Save iamahuman/da71ac3cba4486d28cc51750b355ff8b to your computer and use it in GitHub Desktop.
Save iamahuman/da71ac3cba4486d28cc51750b355ff8b to your computer and use it in GitHub Desktop.
Decompress inner archives (control.tar, data.tar) of Debian packages (.deb)
#define _FILE_OFFSET_BITS 64
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/xattr.h>
#include <sys/wait.h>
#include <fcntl.h>
#define AR_FNAME_OFF 0
#define AR_FNAME_SIZE 16
#define AR_SIZE_OFF 48
#define AR_SIZE_LEN 10
#define AR_FMAG_OFF 58
#define AR_FMAG_LEN 2
#define AR_HDRSZ 60
#define AR_MAGIC "!<arch>\n"
#define AR_FMAG "`\n"
#define FD_DIR_PREFIX "/proc/self/fd/"
#define COMP_EXT_MAX_SIZE 2
#ifdef __GNUC__
#define unlikely(x) (__builtin_expect(!!(x), 0))
#define likely(x) (__builtin_expect(!!(x), 1))
#else
#define unlikely(x) (!!(x))
#define likely(x) (!!(x))
#endif
#define MSG_PREFIX "deb-decompress: "
static struct compressor {
char extension[COMP_EXT_MAX_SIZE + 1];
char program[7 - COMP_EXT_MAX_SIZE];
} const compressors[] = {
{ "gz", "gzip" },
{ "xz", "xz" },
};
static ssize_t
read_all(int fd, void *buffer, size_t len)
{
size_t i;
ssize_t res;
for (i = 0; i < len; i += res)
{
res = read(fd, (unsigned char *) buffer + i, len - i);
if (res == 0)
break;
if (unlikely(res == -1))
return -1;
}
return i;
}
static ssize_t
pread_all(int fd, void *buffer, size_t len, off_t *off)
{
size_t i;
ssize_t res;
if (off == NULL)
return read_all(fd, buffer, len);
for (i = 0; i < len; i += res)
{
res = pread(fd, (unsigned char *) buffer + i, len - i, *off);
if (res == 0)
break;
if (unlikely(res == -1))
return -1;
*off += res;
}
return i;
}
static ssize_t
write_all(int fd, const void *data, size_t len)
{
size_t i;
ssize_t res;
for (i = 0; i < len; i += res)
{
res = write(fd, (unsigned char const *) data + i, len - i);
if (res == 0)
break;
if (unlikely(res == -1))
return -1;
}
return i;
}
static ssize_t
pwrite_all(int fd, const void *data, size_t len, off_t *off)
{
size_t i;
ssize_t res;
if (off == NULL)
return write_all(fd, data, len);
for (i = 0; i < len; i += res)
{
res = pwrite(fd, (unsigned char const *) data + i, len - i, *off);
if (res == 0)
break;
if (unlikely(res == -1))
return -1;
*off += res;
}
return i;
}
static ssize_t
writev_one(int fd, struct iovec **iovp, int* iovcntp)
{
ssize_t res;
size_t rem;
int iovcnt;
struct iovec *cur;
iovcnt = *iovcntp;
if (iovcnt <= 0) return 0;
cur = *iovp;
res = writev(fd, cur, iovcnt);
if (res < 0) return res;
rem = res;
do
{
if (rem < cur->iov_len)
{
cur->iov_base = (unsigned char *)
cur->iov_base + rem;
cur->iov_len -= rem;
rem = 0;
}
else
{
rem -= cur->iov_len;
cur->iov_base = (unsigned char *)
cur->iov_base + cur->iov_len;
cur->iov_len = 0;
cur++;
iovcnt--;
}
} while (iovcnt > 0 && rem > 0);
*iovp = cur;
*iovcntp = iovcnt;
return res;
}
static ssize_t
writev_all(int fd, struct iovec **iovecp, int* iovcntp)
{
ssize_t res, acc = 0;
do
{
res = writev_one(fd, iovecp, iovcntp);
if (unlikely(res < 0)) return res;
acc += res;
} while (res > 0);
return acc;
}
#ifdef __linux__
#define KERNEL_loff_t __off64_t
#else
#define KERNEL_loff_t loff_t
#endif
typedef ssize_t (*copy_fn_t)(int fd_in, KERNEL_loff_t *off_in,
int fd_out, KERNEL_loff_t *off_out,
size_t len, unsigned int flags);
static ssize_t
my_copy_range(int fd_in, KERNEL_loff_t *off_in,
int fd_out, KERNEL_loff_t *off_out,
size_t len, unsigned int flags)
{
int err;
void *mem;
unsigned char *srcptr;
ssize_t nres;
size_t map_len, i, new_i, local_limit;
size_t page_size, pgszm1, start_pad;
KERNEL_loff_t off_i;
(void) flags;
page_size = sysconf(_SC_PAGE_SIZE);
if (unlikely(page_size < 1 || (page_size & (page_size - 1))))
{
if (page_size != (size_t) -1L)
errno = ENOSYS;
return -1;
}
pgszm1 = page_size - 1;
if (off_in == NULL)
{
errno = EINVAL;
return -1;
}
err = 0;
nres = 0;
for (i = 0; i < len; )
{
off_i = *off_in;
start_pad = off_i & pgszm1;
map_len = (len - i + start_pad + pgszm1) & ~pgszm1;
while ((mem = mmap(NULL, map_len, PROT_READ,
MAP_PRIVATE | MAP_POPULATE,
fd_in, off_i - start_pad)) == MAP_FAILED)
{
map_len = (map_len >> 1) & ~pgszm1;
if (unlikely(map_len < 1))
{
nres = -1;
goto fail;
}
}
srcptr = (unsigned char *) mem + start_pad;
local_limit = i + (map_len - start_pad);
if (local_limit > len)
local_limit = len;
for (new_i = i; new_i < local_limit; new_i += nres)
{
nres = off_out != NULL
? pwrite(fd_out, srcptr,
local_limit - new_i, *off_out)
: write(fd_out, srcptr, local_limit - new_i);
if (unlikely(nres == 0 || nres == -1))
break;
if (off_out != NULL)
*off_out += nres;
srcptr += nres;
}
*off_in = off_i + (new_i - i);
i = new_i;
if (nres == -1)
{
err = errno;
(void) munmap(mem, map_len);
break;
}
(void) munmap(mem, map_len);
}
fail:
errno = err;
return nres == -1 ? -1 : (ssize_t) i;
}
static ssize_t
my_read_write(int fd_in, KERNEL_loff_t *off_in,
int fd_out, KERNEL_loff_t *off_out,
size_t len, unsigned int flags)
{
unsigned char *buf;
size_t buflen, i, ki;
ssize_t rres = 0, wres = 0;
(void) flags;
buflen = (size_t) 256 << sizeof(void *);
buf = malloc(buflen);
if (buf == NULL)
return -1;
for (i = 0; i < len; i += ki)
{
size_t rlen = len - i;
if (rlen > buflen) rlen = buflen;
rres = off_in != NULL
? pread(fd_in, buf, rlen, *off_in)
: read(fd_in, buf, rlen);
if (rres == 0 || rres == -1) break;
if (off_in != NULL)
*off_in += rres;
for (ki = 0; ki < (size_t) rres; ki += wres)
{
wres = off_out != NULL
? pwrite(fd_out, buf + ki,
rres - ki, *off_out)
: write(fd_out, buf + ki, rres - ki);
if (unlikely(wres == 0 || wres == -1))
break;
if (off_out != NULL)
*off_out += wres;
}
if (unlikely(wres == 0 || wres == -1)) break;
}
free(buf);
return rres >= 0 && wres >= 0 ? (ssize_t) i : -1;
}
static copy_fn_t const copy_functions[] = {
copy_file_range,
splice,
my_copy_range,
my_read_write,
NULL
};
static KERNEL_loff_t
copy_range(int fd_in, KERNEL_loff_t *off_in,
int fd_out, KERNEL_loff_t *off_out,
KERNEL_loff_t len)
{
copy_fn_t const *cur_fn = &copy_functions[0];
KERNEL_loff_t old_off_in = 0, old_off_out = 0;
KERNEL_loff_t cur_off_in, cur_off_out;
KERNEL_loff_t *poff_in = NULL, *poff_out = NULL;
KERNEL_loff_t i = 0, res, max_chunk =
((KERNEL_loff_t) SSIZE_MAX) & ~((KERNEL_loff_t) 0xfff);
int err;
if (off_in != NULL)
{
old_off_in = *off_in;
poff_in = &cur_off_in;
}
if (off_out != NULL)
{
old_off_out = *off_out;
poff_out = &cur_off_out;
}
while (i < len)
{
KERNEL_loff_t rem_size;
rem_size = len - i;
if (rem_size > max_chunk)
rem_size = max_chunk;
cur_off_in = old_off_in + i;
cur_off_out = old_off_out + i;
res = (*cur_fn)(fd_in, poff_in, fd_out, poff_out, rem_size, 0);
if (unlikely(res == -1))
{
i += cur_off_out - old_off_out;
err = errno;
if (err != EINVAL && err != EXDEV && err != ENOSYS)
break;
if (*++cur_fn == NULL)
break;
}
else
{
i += res;
if (res == 0)
break;
}
}
if (off_in != NULL)
*off_in = old_off_in + i;
if (off_out != NULL)
*off_out = old_off_out + i;
return i;
}
#define VEC_STRLIT(x) { (void *) (x), sizeof(x) - 1 }
#define VEC_STRPTR(x) { (void *) (x), strlen(x) }
static void
report_exec_fail(const char *name)
{
int err = errno;
char const *msg = strerror(err);
struct iovec segs[] = {
VEC_STRLIT("Cannot execute "),
VEC_STRPTR(name),
VEC_STRLIT(": "),
VEC_STRPTR(msg),
};
struct iovec *iovecs = segs;
int iovcnt = sizeof(segs) / sizeof(*segs);
(void) writev_all(STDERR_FILENO, &iovecs, &iovcnt);
_exit(err == ENOENT ? 127 : 126);
}
static int
send_to_pipe(int infd, int outfd, loff_t *offp, loff_t limit)
{
loff_t off, offtmp;
ssize_t res = 0;
off = *offp;
while (off < limit)
{
offtmp = off;
#ifdef __linux__
res = splice(infd, &offtmp, outfd, NULL, limit - off, 0);
#else
res = sendfile(outfd, infd, &offtmp, limit - off);
#endif
if (res == -1 || res == 0) break;
off += res;
}
*offp = off;
return res == -1 ? -1 : 0;
}
struct spawn_proc_args {
char const *workdir;
int (*fn)(char const *name, char *const argv[], char *const envp[]);
char const *name;
char *const *argv;
char *const *envp;
pid_t pid;
int wstatus;
};
static int
run_filter(int infd, int outfd,
loff_t *off, loff_t limit,
struct spawn_proc_args *spawn_args)
{
struct sigaction sa_old, sa_ignore;
int inp_pipefd[2];
int res, err = 0, wstmp;
pid_t pid, newpid;
res = pipe(inp_pipefd);
if (res == -1)
return -1;
pid = fork();
if (pid == 0)
{
char *const *envp;
if (spawn_args->workdir != NULL &&
chdir(spawn_args->workdir) == -1)
goto fail;
(void) close(infd);
(void) close(inp_pipefd[1]);
if (inp_pipefd[0] != STDIN_FILENO)
{
if (dup2(inp_pipefd[0], STDIN_FILENO) == -1)
goto fail;
(void) close(inp_pipefd[0]);
}
if (outfd != STDOUT_FILENO)
{
if (dup2(outfd, STDOUT_FILENO) == -1)
goto fail;
(void) close(outfd);
}
envp = spawn_args->envp;
if (envp == NULL)
envp = environ;
(*spawn_args->fn)(
spawn_args->name,
spawn_args->argv,
envp);
report_exec_fail(spawn_args->name);
fail:
_exit(1);
}
err = errno;
close(inp_pipefd[0]);
if (unlikely(pid < 0))
goto close_pipe;
close(inp_pipefd[0]);
spawn_args->pid = pid;
memset(&sa_ignore, 0, sizeof(sa_ignore));
sa_ignore.sa_handler = SIG_IGN;
sa_ignore.sa_flags = 0;
res = sigaction(SIGPIPE, &sa_ignore, &sa_old);
err = errno;
if (unlikely(res == -1))
goto close_pipe;
res = send_to_pipe(infd, inp_pipefd[1], off, limit);
err = errno;
close(inp_pipefd[1]);
if (memcmp(&sa_ignore, &sa_old, sizeof(sa_old)) != 0)
(void) sigaction(SIGPIPE, &sa_old, &sa_ignore);
for (;;)
{
newpid = waitpid(pid, &wstmp, 0);
if (unlikely(newpid == -1))
{
if (res != -1)
{
err = errno;
res = -1;
}
break;
}
if (newpid == pid)
{
spawn_args->wstatus = wstmp;
if (res == -1) res = -2;
break;
}
}
if (res < 0) errno = err;
return res;
close_pipe:
if (res < 0) errno = err;
close(inp_pipefd[1]);
return res;
}
static int
is_wstatus_ok(int wstatus)
{
int status;
if (!WIFEXITED(wstatus))
return 0;
status = WEXITSTATUS(wstatus);
return status == 0 || status == EXIT_SUCCESS;
}
static int
report_wstatus(FILE *stream, struct spawn_proc_args const *args)
{
int wstatus = args->wstatus;
if (WIFEXITED(wstatus))
{
int status;
status = WEXITSTATUS(wstatus);
return fprintf(stream,
"Program %s (PID %d) "
"exited with status %d\n",
args->name, args->pid, status);
}
if (WIFSIGNALED(wstatus))
{
int sig;
sig = WTERMSIG(wstatus);
return fprintf(stream,
"Program %s (PID %d) "
"terminated with signal %d: %s%s\n",
args->name, args->pid, sig, strsignal(sig),
WCOREDUMP(wstatus) ? " (core dumped)" : "");
}
if (WIFSTOPPED(wstatus))
{
int sig;
sig = WSTOPSIG(wstatus);
return fprintf(stream,
"Program %s (PID %d) "
"stopped with signal %d: %s",
args->name, args->pid, sig, strsignal(sig));
}
#ifdef WIFCONTINUED
if (WIFCONTINUED(wstatus))
{
return fprintf(stream,
"Program %s (PID %d) has continued\n",
args->name, args->pid);
}
#endif
return fprintf(stream,
"Program %s (PID %d) wstatus = %d\n",
args->name, args->pid, wstatus);
}
static char const *
get_compressor_for(char const *name, size_t len)
{
struct compressor const *item;
size_t i;
if (len >= sizeof(item->extension))
return NULL;
for (i = 0; i < sizeof(compressors) / sizeof(*compressors); i++)
{
item = &compressors[i];
if (memcmp(item->extension, name, len) == 0 &&
item->extension[len] == '\0')
{
return item->program;
}
}
return NULL;
}
static size_t
arhdr_get_name_len(char const *header)
{
char const *const filename = header + AR_FNAME_OFF;
char const *end = filename + AR_FNAME_SIZE;
while (end != filename && end[-1] == ' ')
end--;
if (end != filename && end[-1] == '/')
end--;
return end - filename;
}
static loff_t
arhdr_get_size(char const *header, int *has_invalid)
{
loff_t value = 0, digit;
size_t i = 0;
char const *size_field;
*has_invalid = 0;
size_field = header + AR_SIZE_OFF;
for (; i < AR_SIZE_LEN && size_field[i] == ' '; i++)
;
for (; i < AR_SIZE_LEN && size_field[i] != ' '; i++)
{
digit = (loff_t) size_field[i] - (loff_t) '0';
if (digit < 0 || digit > 9)
{
*has_invalid = 1;
}
value = (value * 10) + digit;
}
return value;
}
static int
arhdr_set_size(char *header, loff_t size)
{
char sizebuf[AR_SIZE_LEN], *bufptr, *size_field;
size_t len;
int res = -1;
bufptr = sizebuf + sizeof(sizebuf);
if (size >= 0)
{
res = 0;
do
{
if (bufptr == sizebuf) {
res = -1;
break;
}
*--bufptr = '0' + (size % 10);
size /= 10;
} while (size != 0);
}
len = sizeof(sizebuf) - (bufptr - sizebuf);
size_field = header + AR_SIZE_OFF;
memcpy(size_field, bufptr, len);
memset(size_field + len, ' ', AR_SIZE_LEN - len);
return res;
}
static int
hl_process_one_member(int infd, int outfd, loff_t *off_in, loff_t *off_out)
{
char header[AR_HDRSZ], *filename, *dot;
char const *comp_prog = NULL;
loff_t member_size, res_size;
int has_invalid;
size_t filename_len;
ssize_t n;
n = pread_all(infd, header, sizeof(header), off_in);
if (n == 0)
return 0;
if (n == -1)
{
perror(MSG_PREFIX "reading ar member header");
return -1;
}
if (n != AR_HDRSZ)
{
fputs(MSG_PREFIX "short read for ar member magic\n", stderr);
return -1;
}
if (memcmp(header + AR_FMAG_OFF, AR_FMAG, AR_FMAG_LEN))
{
fputs(MSG_PREFIX "invalid ar member magic\n", stderr);
return -1;
}
member_size = arhdr_get_size(header, &has_invalid);
if (has_invalid)
{
fputs(MSG_PREFIX "warning: invalid ar member size\n", stderr);
}
filename = header + AR_FNAME_OFF;
filename_len = arhdr_get_name_len(header);
dot = filename_len > 1
? (char *) memrchr(filename + 1, '.', filename_len - 1)
: NULL;
if (dot != NULL)
{
char *ext = dot + 1;
comp_prog = get_compressor_for(
ext, (filename + filename_len) - ext);
}
if (comp_prog != NULL)
{
char *const argv[] = { (char *) comp_prog, "-dc", "-", NULL };
struct spawn_proc_args spa = {
/* avoid decompressor from being influenced
* by the current working directory */
"/",
execvpe,
comp_prog, argv, NULL /* use environ(3) */,
-1, 0
};
int res, err;
loff_t hdroff, startoff, endoff;
loff_t seekres, newsize, limit;
hdroff = *off_out;
startoff = hdroff + AR_HDRSZ;
seekres = lseek(outfd, startoff, SEEK_SET);
if (seekres == -1)
{
perror(MSG_PREFIX "lseek SEEK_SET on output file");
return -1;
}
limit = *off_in + member_size;
res = run_filter(infd, outfd, off_in, limit, &spa);
err = errno;
if (res != -1 && !is_wstatus_ok(spa.wstatus))
(void) report_wstatus(stderr, &spa);
if (res < 0)
fprintf(stderr, MSG_PREFIX
"failed to run filter %s: %s\n",
spa.name, strerror(err));
if (res < 0 || !is_wstatus_ok(spa.wstatus))
return -1;
endoff = lseek(outfd, 0, SEEK_CUR);
if (endoff == -1)
{
perror(MSG_PREFIX "lseek SEEK_CUR on output file");
return -1;
}
newsize = endoff - startoff;
if (newsize & 1)
{
ssize_t k;
const char newline = '\n';
k = pwrite(outfd, &newline, 1, endoff);
if (unlikely(k == -1))
{
perror(MSG_PREFIX "writing ar 2byte align padding");
return -1;
}
if (unlikely(k != 1))
{
fputs(MSG_PREFIX "truncated padding", stderr);
return -1;
}
endoff++;
}
*off_in = limit + (member_size & 1);
*off_out = endoff;
if (dot != NULL)
{
char *remptr = dot;
size_t remlen;
if (remptr != filename && remptr[-1] == ' ')
{
*remptr++ = '/';
}
remlen = (filename + AR_FNAME_SIZE) - remptr;
memset(remptr, ' ', remlen);
}
res = arhdr_set_size(header, newsize);
if (res == -1)
{
fprintf(stderr, MSG_PREFIX
"decompressed size %lld out of range\n",
(signed long long int) newsize);
return -1;
}
n = pwrite_all(outfd, header, sizeof(header), &hdroff);
if (n == -1)
{
perror(MSG_PREFIX "writing ar member header "
"for an uncompressed file");
return -1;
}
return 2;
}
n = pwrite_all(outfd, header, sizeof(header), off_out);
if (n == -1)
{
perror(MSG_PREFIX "writing ar member header");
return -1;
}
res_size = copy_range(infd, off_in, outfd, off_out,
member_size + (member_size & 1));
if (res_size == -1)
{
perror(MSG_PREFIX "copying ar member body");
return -1;
}
if (res_size - (member_size & 1) != member_size)
{
fprintf(stderr, MSG_PREFIX
"archive member truncated! (%lld -> %lld)\n",
(signed long long int) member_size,
(signed long long int) res_size - (member_size & 1));
return -1;
}
return 1;
}
static int
hl_process_archive(int infd, int outfd)
{
ssize_t n;
char magicbuf[sizeof(AR_MAGIC) - 1];
loff_t off_in, off_out;
loff_t *poff_in = NULL;
int res, has_mod;
unsigned long int member_idx;
off_in = lseek(infd, 0, SEEK_CUR);
if (off_in != -1)
{
poff_in = &off_in;
}
else if (errno != ESPIPE)
{
perror(MSG_PREFIX "telling current input position");
return -1;
}
off_out = lseek(outfd, 0, SEEK_CUR);
if (off_out == -1)
{
perror(MSG_PREFIX "telling current output position");
return -1;
}
n = pread_all(infd, magicbuf, sizeof(magicbuf), poff_in);
if (n == -1)
{
perror(MSG_PREFIX "reading ar magic");
return -1;
}
if (n != sizeof(AR_MAGIC) - 1 ||
memcmp(magicbuf, AR_MAGIC, sizeof(AR_MAGIC) - 1) != 0)
{
fputs(MSG_PREFIX "input is not an ar archive\n", stderr);
return -1;
}
n = pwrite_all(outfd, AR_MAGIC, sizeof(AR_MAGIC) - 1, &off_out);
if (n == -1)
{
perror(MSG_PREFIX "writing ar magic");
return -1;
}
if (n != sizeof(AR_MAGIC) - 1)
{
fprintf(stderr, MSG_PREFIX
"ar magic only partially written (%lld bytes)\n",
(signed long long int) n);
return -1;
}
has_mod = 0;
member_idx = 0;
while ((res = hl_process_one_member(
infd, outfd, poff_in, &off_out)) > 0)
{
if (res == 2)
has_mod = 1;
member_idx++;
}
if (res < 0)
{
fprintf(stderr,
MSG_PREFIX "failed at member %lu\n"
MSG_PREFIX "input offset: %lld (%#llx)\n"
MSG_PREFIX "output offset: %lld (%#llx)\n",
member_idx,
(signed long long int) off_in,
(unsigned long long int) off_in,
(signed long long int) off_out,
(unsigned long long int) off_out);
}
return res >= 0 ? has_mod : res;
}
ssize_t alloc_list_fd_xattr(int fd, char **strp)
{
void *buf, *newbuf;
ssize_t res, size;
retry:
size = flistxattr(fd, NULL, 0);
if (size == -1)
return -1;
buf = malloc(size);
if (buf == NULL)
return -1;
res = flistxattr(fd, (char *) buf, size);
if (res == 0 || res == -1)
{
free(buf);
if (res != -1)
*strp = NULL;
else if (errno == ERANGE)
goto retry;
return res;
}
if (res < size && (newbuf = realloc(buf, res)) != NULL)
buf = newbuf;
*strp = (char *) buf;
return res;
}
ssize_t alloc_get_fd_xattr(int fd, const char *name, void **valuep)
{
void *buf, *newbuf;
ssize_t res, size;
retry:
size = fgetxattr(fd, name, NULL, 0);
if (size == -1)
return -1;
buf = malloc(size);
if (buf == NULL)
return -1;
res = fgetxattr(fd, name, buf, size);
if (res == 0 || res == -1)
{
free(buf);
if (res != -1)
*valuep = NULL;
else if (errno == ERANGE)
goto retry;
return res;
}
if (res < size && (newbuf = realloc(buf, res)) != NULL)
buf = newbuf;
*valuep = buf;
return res;
}
int hl_copy_xattrs(int srcfd, int destfd)
{
ssize_t len_xattrs, i, xlen;
char *xattrs = NULL;
int res, retv = 0;
len_xattrs = alloc_list_fd_xattr(srcfd, &xattrs);
if (len_xattrs == -1)
{
perror(MSG_PREFIX "retrieving extended attributes"
" of input file");
return -1;
}
for (i = 0; i < len_xattrs; i += xlen + 1)
{
const char *xattr = xattrs + i;
void *value = NULL;
ssize_t vlen;
xlen = strlen(xattr);
vlen = alloc_get_fd_xattr(srcfd, xattr, &value);
if (vlen == -1)
{
if (errno != ENODATA)
{
fprintf(stderr, MSG_PREFIX
"retrieving extended attribute "
"%s: %s\n",
xattr, strerror(errno));
retv = -1;
}
continue;
}
res = fsetxattr(destfd, xattr, value, vlen, 0);
if (res == -1)
{
fprintf(stderr, MSG_PREFIX
"setting extended attribute %s: %s\n",
xattr, strerror(errno));
retv = -1;
}
free(value);
}
free(xattrs);
return retv;
}
int hl_copy_attrs(int srcfd, int destfd)
{
struct stat statbuf;
struct timespec times[2];
int res;
res = fstat(srcfd, &statbuf);
if (res == -1)
{
perror(MSG_PREFIX "fstat on input file");
return -1;
}
/* Do fchmod later, lest the modes contain set[ug]id bits */
res = fchown(destfd, statbuf.st_uid, statbuf.st_gid);
if (res == -1)
{
perror(MSG_PREFIX "fchown on output file");
}
res = fchmod(destfd, statbuf.st_mode);
if (res == -1)
{
perror(MSG_PREFIX "fchmod on output file");
}
times[0] = statbuf.st_atim;
times[1] = statbuf.st_mtim;
res = futimens(destfd, times);
if (res == -1)
{
perror(MSG_PREFIX "futimens on output file");
}
(void) hl_copy_xattrs(srcfd, destfd);
return 0;
}
int gen_fdpath(char *fdpath, size_t fdpath_size, int fd)
{
unsigned int num;
size_t pfxlen = sizeof(FD_DIR_PREFIX) - 1, numlen;
char *ptr, *numptr, *endptr;
if (fdpath_size <= pfxlen)
{
errno = ERANGE;
return -1;
}
if (fd < 0)
{
errno = EBADF;
return -1;
}
numptr = fdpath + pfxlen;
endptr = fdpath + (fdpath_size - 1);
ptr = endptr;
num = (unsigned int) fd;
do
{
if (ptr == numptr) {
errno = ERANGE;
return -1;
}
*--ptr = '0' + (num % 10);
num /= 10;
} while (num != 0);
numlen = endptr - ptr;
memcpy(fdpath, FD_DIR_PREFIX, pfxlen);
memmove(numptr, ptr, numlen);
numptr[numlen] = '\0';
return 0;
}
int main(int argc, char **argv)
{
mode_t const world_rw =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
char *inp_name, *endslash;
char const *base_name;
int res, infd, outfd, sysres;
unsigned int tmp_cnt;
if (argc > 1 && strcmp(argv[1], "--") == 0)
argc--, argv++;
if (argc == 1)
{
res = hl_process_archive(STDIN_FILENO, STDOUT_FILENO);
}
else if (argc == 2)
{
char fdpath[sizeof(FD_DIR_PREFIX) + 10];
char *tmp_name;
size_t base_name_len, suffix_len = 10;
inp_name = argv[1];
endslash = strrchr(inp_name, '/');
if (endslash != NULL)
{
*endslash = '\0';
res = chdir(inp_name);
if (res == -1)
{
perror(MSG_PREFIX
"entering directory of target file");
return EXIT_FAILURE;
}
base_name = endslash + 1;
}
else
{
base_name = inp_name;
}
base_name_len = strlen(base_name);
tmp_name = malloc(base_name_len + suffix_len + 1);
if (tmp_name == NULL)
{
perror(MSG_PREFIX
"allocating buffer for temporary file name");
return EXIT_FAILURE;
}
infd = open(base_name, O_RDONLY | O_CLOEXEC | O_NOCTTY);
if (infd == -1)
{
perror(MSG_PREFIX "opening input file");
res = -1;
goto free_tmp_name;
}
outfd = open(".",
O_WRONLY | O_CLOEXEC | O_NOCTTY | O_TMPFILE, 0);
if (outfd == -1)
{
perror(MSG_PREFIX "creating output temporary file");
res = -1;
goto close_in;
}
res = hl_process_archive(infd, outfd);
if (res <= 0)
goto close_out;
(void) hl_copy_attrs(infd, outfd);
sysres = gen_fdpath(fdpath, sizeof(fdpath), outfd);
if (sysres == -1)
{
perror(MSG_PREFIX "generating "
"/proc/self/fd/<fd> path");
res = -1;
goto close_out;
}
memcpy(tmp_name, base_name, base_name_len);
tmp_name[base_name_len] = '~';
tmp_name[base_name_len + 1] = '\0';
tmp_cnt = 0;
retry:
sysres = linkat(AT_FDCWD, fdpath,
AT_FDCWD, tmp_name, AT_SYMLINK_FOLLOW);
if (sysres == -1)
{
if (++tmp_cnt < 1000) {
snprintf(tmp_name + base_name_len + 1,
suffix_len, "%u", tmp_cnt);
goto retry;
}
perror(MSG_PREFIX "creating hard link for output file");
res = -1;
goto close_out;
}
sysres = rename(tmp_name, base_name);
if (sysres == -1)
{
perror(MSG_PREFIX "replacing input file "
"with output temporary file");
res = -1;
goto close_out;
}
close_out:
close(outfd);
close_in:
close(infd);
free_tmp_name:
free(tmp_name);
}
else if (argc == 3)
{
infd = open(argv[1], O_RDONLY | O_CLOEXEC | O_NOCTTY);
if (infd == -1)
{
perror(MSG_PREFIX "opening input file");
return EXIT_FAILURE;
}
outfd = open(argv[2],
O_WRONLY | O_CLOEXEC | O_NOCTTY | O_CREAT,
world_rw);
if (outfd == -1)
{
perror(MSG_PREFIX "opening output file");
close(infd);
return EXIT_FAILURE;
}
res = hl_process_archive(infd, outfd);
if (res >= 0)
{
(void) hl_copy_attrs(infd, outfd);
}
close(outfd);
close(infd);
}
else
{
fputs(MSG_PREFIX "invalid number of arguments\n", stderr);
res = -1;
}
return res >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment