shadow/debian/tar.c

410 lines
8.4 KiB
C
Raw Normal View History

/*
* $Id: tar.c,v 1.2 1999/03/07 19:14:24 marekm Exp $
*
* This is a wrapper for tar to ensure that all files within the
* newly created tar archive have the owner and group set to
* root:root. This makes it possible to build Debian packages
* without root privileges (normally needed to chown files).
*
* Assumptions:
* - the directory containing this program is listed in $PATH
* before the directory containing the real tar program (/bin)
* - the options passed to tar cause it to output the archive
* (not compressed) on standard output
*
* Written by Marek Michalkiewicz <marekm@linux.org.pl>,
* public domain, no warranty, etc.
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <fnmatch.h>
#include <tar.h>
#ifndef REAL_TAR
#define REAL_TAR "/bin/tar"
#endif
#define RECORD_SIZE 512
union record {
char data[RECORD_SIZE];
struct header {
char name[100]; /* NUL-terminated if NUL fits */
char mode[8]; /* 0+ spaces, 1-6 octal digits, space, NUL */
char uid[8]; /* format same as mode */
char gid[8]; /* format same as mode */
char size[12]; /* 0+ spaces, 1-11 octal digits, space */
/* if '1' <= typeflag <= '6', ignore size */
char mtime[12]; /* format same as size */
char chksum[8]; /* 0+ spaces, 1-6 octal digits, NUL, space */
char typeflag;
char linkname[100]; /* NUL-terminated if NUL fits */
/* XXX - for GNU tar, magic is "ustar " (no NUL) and version is " \0" */
char magic[6]; /* must be TMAGIC (NUL term.) */
char version[2]; /* must be TVERSION */
char uname[32]; /* NUL-terminated */
char gname[32]; /* NUL-terminated */
char devmajor[8];
char devminor[8];
#ifdef GNU_TAR_FORMAT
char atime[12];
char ctime[12];
char offset[12];
char longnames[4];
char pad;
struct sparse {
char offset[12];
char numbytes[12];
} sp[4];
char isextended;
char realsize[12];
#else
/* if prefix[0] != NUL then filename = prefix/name else filename = name */
char prefix[155]; /* NUL-terminated if NUL fits */
#endif
} h;
#ifdef GNU_TAR_FORMAT
struct exthdr {
struct sparse sp[21];
char isextended;
} xh;
#endif
};
static union record tarbuf;
static int infd = -1, outfd = -1;
int main(int, char **);
static ssize_t xread(int, char *, size_t);
static ssize_t xwrite(int, const char *, size_t);
static int block_is_eof(void);
static void block_read(void);
static void block_write(void);
static void verify_magic(void);
static void verify_checksum(void);
static void update_checksum(void);
static void set_owner(const char *);
static void set_group(const char *);
static void process_archive(void);
int
main(int argc, char **argv)
{
int pipefd[2];
pid_t pid;
const char *real_tar;
int status;
real_tar = getenv("REAL_TAR");
if (!real_tar)
real_tar = REAL_TAR;
if (pipe(pipefd)) {
perror("pipe");
exit(1);
}
pid = fork();
if (pid == 0) { /* child */
/* redirect stdout to the pipe */
if (dup2(pipefd[1], STDOUT_FILENO) != 1) {
perror("dup2");
_exit(126);
}
close(pipefd[0]);
close(pipefd[1]);
/* run the real tar program */
execv(real_tar, argv);
if (errno == ENOENT) {
perror("execve");
_exit(127);
} else {
perror("execve");
_exit(126);
}
} else if (pid < 0) { /* error */
perror("fork");
exit(1);
}
/* parent */
close(pipefd[1]);
/* read from pipefd[0], modify tar headers, write to stdout ... */
infd = pipefd[0];
outfd = STDOUT_FILENO;
process_archive();
/* wait for the tar subprocess to finish, and return its exit status */
status = 1;
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid");
exit(1);
}
if (WIFSIGNALED(status)) {
kill(getpid(), WTERMSIG(status));
exit(1);
}
exit(WEXITSTATUS(status));
}
/* EINTR-safe versions of read() and write() - they don't really help much
as GNU tar itself (version 1.11.8 at least) is not EINTR-safe, but it
doesn't hurt... Also, these functions never return errors - instead,
they print an error message to stderr, and exit(1). End of file is
indicated by returning the number of bytes actually read. */
static ssize_t
xread(int fd, char *buf, size_t count)
{
ssize_t n;
size_t left;
left = count;
do {
n = read(fd, buf, left);
if ((n < 0) && (errno == EINTR))
continue;
if (n <= 0)
break;
left -= n;
buf += n;
} while (left > 0);
if (count > left)
return count - left;
if (n < 0) {
perror("read");
exit(1);
}
return 0;
}
static ssize_t
xwrite(int fd, const char *buf, size_t count)
{
ssize_t n;
size_t left;
left = count;
do {
n = write(fd, buf, left);
if (n < 0) {
if (errno == EINTR)
continue;
/* any other write errors are fatal */
perror("write");
exit(1);
}
left -= n;
buf += n;
} while (left > 0);
return count;
}
static int
block_is_eof(void)
{
unsigned int i;
for (i = 0; i < sizeof(tarbuf.data); i++) {
if (tarbuf.data[i])
return 0;
}
return 1;
}
static void
block_read(void)
{
ssize_t nread;
nread = xread(infd, tarbuf.data, RECORD_SIZE);
if (nread != RECORD_SIZE) {
fprintf(stderr, "unexpected end of file\n");
exit(1);
}
}
static void
block_write(void)
{
xwrite(outfd, tarbuf.data, RECORD_SIZE);
}
static void
verify_magic(void)
{
/* only check that magic starts with "ustar" - works for
standard UNIX tar as well as GNU tar formats. */
if (strncmp(tarbuf.h.magic, "ustar", 5) != 0) {
fprintf(stderr, "bad tar header magic\n");
exit(1);
}
}
static void
verify_checksum(void)
{
unsigned int i;
int csum;
if (sscanf(tarbuf.h.chksum, "%o", &csum) != 1) {
fprintf(stderr, "bad tar checksum format\n");
exit(1);
}
memset(tarbuf.h.chksum, ' ', sizeof(tarbuf.h.chksum));
for (i = 0; i < sizeof(tarbuf.data); i++)
csum -= (unsigned char) tarbuf.data[i];
if (csum) {
fprintf(stderr, "bad tar checksum value\n");
exit(1);
}
}
static void
update_checksum(void)
{
unsigned int i;
int csum;
memset(tarbuf.h.chksum, ' ', sizeof(tarbuf.h.chksum));
csum = 0;
for (i = 0; i < sizeof(tarbuf.data); i++)
csum += (unsigned char) tarbuf.data[i];
snprintf(tarbuf.h.chksum, sizeof(tarbuf.h.chksum), "%6o", csum);
}
static void
set_owner(const char *username)
{
const struct passwd *pw;
pw = getpwnam(username);
memset(tarbuf.h.uname, 0, sizeof(tarbuf.h.uname));
snprintf(tarbuf.h.uname, sizeof(tarbuf.h.uname), "%s", username);
snprintf(tarbuf.h.uid, sizeof(tarbuf.h.uid), "%6o ", (int) (pw ? pw->pw_uid : 0));
}
static void
set_group(const char *groupname)
{
const struct group *gr;
gr = getgrnam(groupname);
memset(tarbuf.h.gname, 0, sizeof(tarbuf.h.gname));
snprintf(tarbuf.h.gname, sizeof(tarbuf.h.gname), "%s", groupname);
snprintf(tarbuf.h.gid, sizeof(tarbuf.h.gid), "%6o ", (int) (gr ? gr->gr_gid : 0));
}
static void
process_archive(void)
{
ssize_t nread;
long size;
size = 0;
for (;;) {
/* read the header or data block */
block_read();
/* copy data blocks, if any */
if (size > 0) {
block_write();
size -= RECORD_SIZE;
continue;
}
if (block_is_eof()) {
/* eof marker */
block_write();
break;
}
verify_magic();
verify_checksum();
/* process the header */
switch (tarbuf.h.typeflag) {
case LNKTYPE:
case SYMTYPE:
case CHRTYPE:
case BLKTYPE:
case DIRTYPE:
case FIFOTYPE:
/* no data blocks - ignore size */
break;
case REGTYPE:
case AREGTYPE:
case CONTTYPE:
default:
if (sscanf(tarbuf.h.size, "%lo", &size) != 1) {
fprintf(stderr, "bad size format\n");
exit(1);
}
break;
}
/* XXX - for now, just chown all files to root:root. */
set_owner("root");
set_group("root");
update_checksum();
/* write the modified header */
block_write();
}
/* eof marker detected, copy anything beyond it */
for (;;) {
nread = xread(infd, tarbuf.data, RECORD_SIZE);
if (nread == 0)
break; /* end of file */
xwrite(outfd, tarbuf.data, (size_t) nread);
}
}
#if 0
/* permission specification file format, fixperms-1.00 compatible:
type filename owner group mode [linkname | major minor] [# comment]
type:
- = regular file
l = link
d = directory
c = char dev
b = block dev
p = fifo
s = socket
filename - absolute pathname, wildcards ok [not for fixperms]
linkname - only for type l
major, minor - only for type c or b
owner group - numeric, or names [not for fixperms]
XXX not yet implemented
*/
struct permspec {
char *name;
uid_t uid;
gid_t gid;
mode_t mode;
char *uname;
char *gname;
char *linkname;
dev_t dev;
struct permspec *next;
};
#endif