Created
April 4, 2021 08:15
-
-
Save iamahuman/da71ac3cba4486d28cc51750b355ff8b to your computer and use it in GitHub Desktop.
Decompress inner archives (control.tar, data.tar) of Debian packages (.deb)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 = ©_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