From bd49e931f20cf4f25d0c085cf7b048efe16cb93b Mon Sep 17 00:00:00 2001 From: kaiw Date: Thu, 21 Feb 2008 10:52:31 +0000 Subject: [PATCH] Import ar(1) front-end. (aka 'BSD' ar) Reviewed by: jkoshy Approved by: jkoshy (mentor) Tested by: erwin (ports build test on pointyhat) Sponsored by: Google Summer of Code 2007 Reviewed by (earlier version): Jaakko Heinonen Tested by (earlier version): Steve Kargl Tested by (earlier version): Martin Voros Tested by (earlier version): swell.k[AT]gmail.com Tested by (earlier version): joel Tested by (earlier version): Alexey Shuvaev Tested by (earlier version): Arjan van Leeuwen Thanks to gabor@ for building ports for it. Thanks to erwin@ and kris@ for scheduling the ports build test on pointyhat. And thanks to many others for their feedback. --- usr.bin/ar/Makefile | 12 + usr.bin/ar/ar.1 | 407 +++++++++++++++++++++++ usr.bin/ar/ar.c | 377 +++++++++++++++++++++ usr.bin/ar/ar.h | 119 +++++++ usr.bin/ar/read.c | 204 ++++++++++++ usr.bin/ar/util.c | 86 +++++ usr.bin/ar/write.c | 784 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1989 insertions(+) create mode 100644 usr.bin/ar/Makefile create mode 100644 usr.bin/ar/ar.1 create mode 100644 usr.bin/ar/ar.c create mode 100644 usr.bin/ar/ar.h create mode 100644 usr.bin/ar/read.c create mode 100644 usr.bin/ar/util.c create mode 100644 usr.bin/ar/write.c diff --git a/usr.bin/ar/Makefile b/usr.bin/ar/Makefile new file mode 100644 index 000000000000..6c277eaf6834 --- /dev/null +++ b/usr.bin/ar/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +PROG= ar +VERSION= 1.0.2 +SRCS= ar.c read.c util.c write.c +WARNS?= 5 +DPADD= ${LIBARCHIVE} ${LIBELF} +LDADD= -larchive -lelf +CFLAGS+= -DBSDAR_VERSION=\"${VERSION}\" +LINKS= ${BINDIR}/ar ${BINDIR}/ranlib + +.include diff --git a/usr.bin/ar/ar.1 b/usr.bin/ar/ar.1 new file mode 100644 index 000000000000..0982bda75aca --- /dev/null +++ b/usr.bin/ar/ar.1 @@ -0,0 +1,407 @@ +.\" Copyright (c) 2007 Joseph Koshy. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" This software is provided by Joseph Koshy ``as is'' and +.\" any express or implied warranties, including, but not limited to, the +.\" implied warranties of merchantability and fitness for a particular purpose +.\" are disclaimed. in no event shall Joseph Koshy be liable +.\" for any direct, indirect, incidental, special, exemplary, or consequential +.\" damages (including, but not limited to, procurement of substitute goods +.\" or services; loss of use, data, or profits; or business interruption) +.\" however caused and on any theory of liability, whether in contract, strict +.\" liability, or tort (including negligence or otherwise) arising in any way +.\" out of the use of this software, even if advised of the possibility of +.\" such damage. +.\" +.\" $FreeBSD$ +.\" +.Dd August 31, 2007 +.Os +.Dr AR 1 +.Dt RANLIB 1 +.Sh NAME +.Nm ar , +.Nm ranlib +.Nd manage archives +.Sh SYNOPSIS +.Nm +.Fl d +.Op Fl T +.Op Fl j +.Op Fl v +.Op Fl z +.Ar archive +.Ar files ... +.Nm +.Fl m +.Op Fl T +.Op Fl a Ar position-after +.Op Fl b Ar position-before +.Op Fl i Ar position-before +.Op Fl j +.Op Fl s +.Op Fl z +.Ar archive +.Ar files ... +.Nm +.Fl p +.Op Fl T +.Op Fl v +.Ar archive +.Op Ar files ... +.Nm +.Fl r +.Op Fl T +.Op Fl a Ar position-after +.Op Fl b Ar position-before +.Op Fl c +.Op Fl i Ar position-before +.Op Fl j +.Op Fl s +.Op Fl u +.Op Fl v +.Op Fl z +.Ar archive +.Ar files ... +.Nm +.Fl s +.Op Fl j +.Op Fl z +.Ar archive +.Nm +.Fl t +.Op Fl T +.Op Fl v +.Ar archive +.Op Ar files ... +.Nm +.Fl x +.Op Fl C +.Op Fl T +.Op Fl o +.Op Fl u +.Op Fl v +.Ar archive +.Op Ar files ... +.Nm ranlib +.Ar archive ... +.Sh DESCRIPTION +The +.Nm +utility creates and maintains groups of files combined into an +archive. +Once an archive has been created, new files can be added to it, and +existing files can be extracted, deleted or replaced. +.Pp +Files are named in the archive by their last file name component, +so if a file referenced by a path containing a +.Dq / +is archived, it will be named by the last component of the path. +Similarly when matching paths listed on the command line against +file names stored in the archive, only the last component of the +path will be compared. +.Pp +The normal use of +.Nm +is for the creation and maintenance of libraries suitable for use +with the link editor +.Xr ld 1 , +although it is not restricted to this purpose. +The +.Nm +utility can create and manage an archive symbol table (see +.Xr ar 5 ) +used to speed up link editing operations. +If a symbol table is present in an archive, it will be +kept upto-date by subsequent operations on the archive (excepting +the quick update specified by the +.Fl q +option). +.Pp +The +.Nm ranlib +utility is used to add an archive symbol table +to an existing archive. +.Sh OPTIONS +The +.Nm +utility supports the following options: +.Bl -tag -width indent +.It Fl a Ar member-after +When used with option +.Fl m +this option specifies that the archive members specified by +arguments +.Ar files ... +are moved to after the archive member named by argument +.Ar member-after . +When used with option +.Fl r +this option specifies that the files specified by arguments +.Ar files ... +are added after the archive member named by argument +.Ar member-after . +.It Fl b Ar member-before +When used with option +.Fl m +this option specifies that the archive members specified by +arguments +.Ar files ... +are moved to before the archive member named by argument +.Ar member-before . +When used with option +.Fl r +this option specifies that the files specified by arguments +.Ar files ... +are added before the archive member named by argument +.Ar member-before . +.It Fl c +Suppress the informational message printed when a new archive is +created using the +.Fl r +and +.Fl q +options. +.It Fl C +Prevent extracted files from replacing like-named files +in the file system. +.It Fl d +Delete the members named by arguments +.Ar files ... +from the archive specified by argument +.Ar archive . +The archive's symbol table, if present, is updated to reflect +the new contents of the archive. +.It Fl f +Synonymous with option +.Fl T . +.It Fl i Ar member-before +Synonymous with option +.Fl b . +.It Fl j +Compress the resulting archive with +.Xr bzip2 1 . +.It Fl m +Move archive members specified by arguments +.Ar files ... +within the archive. +If a position has been specified by one of the +.Fl a , +.Fl b +or +.Fl i +options, the members are moved to before or after the specified +position. +If no position has been specified, the specified members are moved +to the end of the archive. +If the archive has a symbol table, it is updated to reflect the +new contents of the archive. +.It Fl o +Preserve the original modification times of members when extracting +them. +.It Fl p +Write the contents of the specified archive members named by +arguments +.Ar files ... +to standard output. +If no members were specified, the contents of all the files in the +archive are written in the order they appear in the archive. +.It Fl q +Append the files specified by arguments +.Ar files ... +to the archive specified by argument +.Ar archive +without checking if the files already exist in the archive and +without updating the archive's symbol table. +If the archive file +.Ar archive +does not already exist, a new archive is created. +However, to be compatible with GNU +.Nm , +.Fl q +is implemented as a synonym for +.Fl r . +.It Fl r +Replace (add) the files specified by arguments +.Ar files ... +in the archive specified by argument +.Ar archive , +creating the archive if necessary. +Files that replace existing files do not change the order of files +within the archive. +If a file named in arguments +.Ar files ... +does not exist, existing members in the archive that match that +name are not changed. +New files are added to the end of the archive unless one of the +positioning options +.Fl a , +.Fl b +or +.Fl i +is specified. +The archive symbol table, if it exists, is updated to reflect the +new state of the archive. +.It Fl s +Add an archive symbol table (see +.Xr ar 5 ) +to the archive specified by argument +.Ar archive . +Invoking +.Nm +with the +.Fl s +option alone is equivalent to invoking +.Nm ranlib . +.It Fl t +List the files specified by arguments +.Ar files ... +in the order in which they appear in the archive, one per line. +If no files are specified, all files in the archive are listed. +.It Fl T +Use only the first fifteen characters of the archive member name or +command line file name argument when naming archive members. +.It Fl u +Conditionally update the archive or extract members. +When used with the +.Fl r +option, files named by arguments +.Ar files ... +will be replaced in the archive if they are newer than their +archived versions. +When used with the +.Fl x +option, the members specified by arguments +.Ar files ... +will be extracted only if they are newer than the corresponding +files in the file system. +.It Fl v +Provide verbose output. +When used with the +.Fl d , +.Fl m , +.Fl q +or +.Fl x +options, +.Nm +gives a file-by-file description of the archive modification being +performed, which consists of three white-space seperated fields: +the option letter, a dash +.Dq "-" , +and the file name. +When used with the +.Fl r +option, +.Nm +displays the description as above, but the initial letter is an +.Dq a +if the file is added to the archive, or an +.Dr r +if the file replaces a file already in the archive. +When used with the +.Fl p +option, the name of the file enclosed in +.Dq < +and +.Dq > +characters is written to standard output preceded by a single newline +character and followed by two newline characters. +The contents of the named file follow the file name. +When used with the +.Fl t +option, +.Nm +displays eight whitespace separated fields: +the file permissions as displayed by +.Xr strmode 3 , +decimal user and group IDs separated by a slash ( +.Dq / Ns ) , +the file size in bytes, the file modification time in +.Xr strftime 3 +format +.Dq "%b %e %H:%M %Y" , +and the name of the file. +.It Fl x +Extract archive members specified by arguments +.Ar files ... +into the current directory. +If no members have been specified, extract all members of the archive. +If the file corresponding to an extracted member does not exist it +will be created. +If the file corresponding to an extracted member does exist, its owner +and group will not be changed while its contents will be overwritten +and its permissions will set to that entered in the archive. +The file's access and modification time would be that of the time +of extraction unless the +.Fl o +option was specified. +.It Fl z +Compress the resulting archive with +.Xr gzip 1 . +.El +.Sh EXAMPLES +To create a new archive +.Pa ex.a +containing three files +.Pa ex1.o , +.Pa ex2.o +and +.Pa ex3.o , +use: +.Dl "ar -rc ex.a ex1.o ex2.o ex3.o" +.Pp +To add an archive symbol table to an existing archive +.Pa ex.a , +use: +.Dl "ar -s ex.a" +.Pp +To delete file +.Pa ex1.o +from archive +.Pa ex.a , +use: +.D1 "ar -d ex.a ex1.o" +.Pp +To verbosely list the contents of archive +.Pa ex.a , +use: +.D1 "ar -tv ex.a" +.Sh DIAGNOSTICS +.Ex -std +.Sh SEE ALSO +.Xr ld 1 , +.Xr archive 3 , +.Xr elf 3 , +.Xr strftime 3 , +.Xr strmode 3 , +.Xr ar 5 +.\" .Sh COMPATIBILITY +.\" .Nm +.\" is expected to be compatible with GNU and SVR4 +.\" .Nm . +.\" .Sh STANDARDS +.\" Do the POSIX/SuSv3 standards have anything to say about AR(1)? +.Sh HISTORY +An +.Nm +command first appeared in AT&T UNIX Version 1. +In +.Fx 8 , +.An "Kai Wang" Aq kaiw@FreeBSD.org +reimplemented +.Nm ar +and +.Nm ranlib +using the +.Lb libarchive +and the +.Lb libelf . diff --git a/usr.bin/ar/ar.c b/usr.bin/ar/ar.c new file mode 100644 index 000000000000..2399f9f78dbc --- /dev/null +++ b/usr.bin/ar/ar.c @@ -0,0 +1,377 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 Tim Kientzle + * Copyright (c) 2007 Joseph Koshy + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Hugh Smith at The University of Guelph. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar.h" + +enum options +{ + OPTION_HELP +}; + +static struct option longopts[] = +{ + {"help", no_argument, NULL, OPTION_HELP}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} +}; + +static void bsdar_usage(void); +static void ranlib_usage(void); +static void set_mode(struct bsdar *bsdar, char opt); +static void only_mode(struct bsdar *bsdar, const char *opt, + const char *valid_modes); +static void bsdar_version(void); +static void ranlib_version(void); + +int +main(int argc, char **argv) +{ + struct bsdar *bsdar, bsdar_storage; + char *p; + size_t len; + int i, opt; + + bsdar = &bsdar_storage; + memset(bsdar, 0, sizeof(*bsdar)); + + if ((bsdar->progname = getprogname()) == NULL) + bsdar->progname = "ar"; + + if (strcmp(bsdar->progname, "ranlib") == 0) { + while ((opt = getopt_long(argc, argv, "tV", longopts, + NULL)) != -1) { + switch(opt) { + case 't': + /* Ignored. */ + break; + case 'V': + ranlib_version(); + break; + case OPTION_HELP: + ranlib_usage(); + default: + ranlib_usage(); + } + } + argv += optind; + argc -= optind; + + if (*argv == NULL) + ranlib_usage(); + + bsdar->options |= AR_S; + for (;(bsdar->filename = *argv++) != NULL;) + ar_mode_s(bsdar); + + exit(EX_OK); + } else { + if (argc < 2) + bsdar_usage(); + + if (*argv[1] != '-') { + len = strlen(argv[1]) + 2; + if ((p = malloc(len)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, + "malloc failed"); + *p = '-'; + (void)strlcpy(p + 1, argv[1], len - 1); + argv[1] = p; + } + } + + while ((opt = getopt_long(argc, argv, "abCcdfijlmopqrSsTtuVvxz", + longopts, NULL)) != -1) { + switch(opt) { + case 'a': + bsdar->options |= AR_A; + break; + case 'b': + case 'i': + bsdar->options |= AR_B; + break; + case 'C': + bsdar->options |= AR_CC; + break; + case 'c': + bsdar->options |= AR_C; + break; + case 'd': + set_mode(bsdar, opt); + break; + case 'f': + case 'T': + bsdar->options |= AR_TR; + break; + case 'j': + bsdar->options |= AR_J; + break; + case 'l': + /* ignored, for GNU ar comptibility */ + break; + case 'm': + set_mode(bsdar, opt); + break; + case 'o': + bsdar->options |= AR_O; + break; + case 'p': + set_mode(bsdar, opt); + break; + case 'q': + set_mode(bsdar, opt); + break; + case 'r': + set_mode(bsdar, opt); + break; + case 'S': + bsdar->options |= AR_SS; + break; + case 's': + bsdar->options |= AR_S; + break; + case 't': + set_mode(bsdar, opt); + break; + case 'u': + bsdar->options |= AR_U; + break; + case 'V': + bsdar_version(); + break; + case 'v': + bsdar->options |= AR_V; + break; + case 'x': + set_mode(bsdar, opt); + break; + case 'z': + bsdar->options |= AR_Z; + break; + case OPTION_HELP: + bsdar_usage(); + default: + bsdar_usage(); + } + } + + argv += optind; + argc -= optind; + + if (*argv == NULL) + bsdar_usage(); + + if (bsdar->options & AR_A && bsdar->options & AR_B) + bsdar_errc(bsdar, EX_USAGE, 0, + "only one of -a and -[bi] options allowed"); + + if (bsdar->options & AR_J && bsdar->options & AR_Z) + bsdar_errc(bsdar, EX_USAGE, 0, + "only one of -j and -z options allowed"); + + if (bsdar->options & AR_S && bsdar->options & AR_SS) + bsdar_errc(bsdar, EX_USAGE, 0, + "only one of -s and -S options allowed"); + + if (bsdar->options & (AR_A | AR_B)) { + if ((bsdar->posarg = *argv) == NULL) + bsdar_errc(bsdar, EX_USAGE, 0, + "no position operand specified"); + if ((bsdar->posarg = basename(bsdar->posarg)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, + "basename failed"); + argc--; + argv++; + } + + if (bsdar->options & AR_A) + only_mode(bsdar, "-a", "mqr"); + if (bsdar->options & AR_B) + only_mode(bsdar, "-b", "mqr"); + if (bsdar->options & AR_C) + only_mode(bsdar, "-c", "qr"); + if (bsdar->options & AR_CC) + only_mode(bsdar, "-C", "x"); + if (bsdar->options & AR_O) + only_mode(bsdar, "-o", "x"); + if (bsdar->options & AR_SS) + only_mode(bsdar, "-S", "mqr"); + if (bsdar->options & AR_U) + only_mode(bsdar, "-u", "qrx"); + + if ((bsdar->filename = *argv) == NULL) + bsdar_usage(); + + bsdar->argc = --argc; + bsdar->argv = ++argv; + + if ((!bsdar->mode || strchr("ptx", bsdar->mode)) && + bsdar->options & AR_S) { + ar_mode_s(bsdar); + if (!bsdar->mode) + exit(EX_OK); + } + + switch(bsdar->mode) { + case 'd': + ar_mode_d(bsdar); + break; + case 'm': + ar_mode_m(bsdar); + break; + case 'p': + ar_mode_p(bsdar); + break; + case 'q': + ar_mode_r(bsdar); + break; + case 'r': + ar_mode_r(bsdar); + break; + case 't': + ar_mode_t(bsdar); + break; + case 'x': + ar_mode_x(bsdar); + break; + default: + bsdar_usage(); + /* NOTREACHED */ + } + + for (i = 0; i < bsdar->argc; i++) + if (bsdar->argv[i] != NULL) + bsdar_warnc(bsdar, 0, "%s: not found in archive", + bsdar->argv[i]); + + exit(EX_OK); +} + +static void +set_mode(struct bsdar *bsdar, char opt) +{ + + if (bsdar->mode != '\0' && bsdar->mode != opt) + bsdar_errc(bsdar, EX_USAGE, 0, + "Can't specify both -%c and -%c", opt, bsdar->mode); + bsdar->mode = opt; +} + +static void +only_mode(struct bsdar *bsdar, const char *opt, const char *valid_modes) +{ + + if (strchr(valid_modes, bsdar->mode) == NULL) + bsdar_errc(bsdar, EX_USAGE, 0, + "Option %s is not permitted in mode -%c", opt, bsdar->mode); +} + +static void +bsdar_usage() +{ + + (void)fprintf(stderr, "usage: ar -d [-Tjsvz] archive file ...\n"); + (void)fprintf(stderr, "\tar -m [-Tjsvz] archive file ...\n"); + (void)fprintf(stderr, "\tar -m [-Tabijsvz] position archive file ...\n"); + (void)fprintf(stderr, "\tar -p [-Tv] archive [file ...]\n"); + (void)fprintf(stderr, "\tar -r [-Tcjsuvz] archive file ...\n"); + (void)fprintf(stderr, "\tar -r [-Tabcijsuvz] position archive file ...\n"); + (void)fprintf(stderr, "\tar -s [-jz] archive\n"); + (void)fprintf(stderr, "\tar -t [-Tv] archive [file ...]\n"); + (void)fprintf(stderr, "\tar -x [-CTouv] archive [file ...]\n"); + (void)fprintf(stderr, "\tar -V\n"); + exit(EX_USAGE); +} + +static void +ranlib_usage() +{ + + (void)fprintf(stderr, "usage: ranlib [-t] archive ...\n"); + (void)fprintf(stderr, "\tranlib -V\n"); + exit(EX_USAGE); +} + +static void +bsdar_version() +{ + (void)printf("BSD ar %s - %s\n", BSDAR_VERSION, archive_version()); + exit(EX_OK); +} + +static void +ranlib_version() +{ + (void)printf("ranlib %s - %s\n", BSDAR_VERSION, archive_version()); + exit(EX_OK); +} diff --git a/usr.bin/ar/ar.h b/usr.bin/ar/ar.h new file mode 100644 index 000000000000..aaad1b9b2f8c --- /dev/null +++ b/usr.bin/ar/ar.h @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2007 Kai Wang + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * ar(1) options. + */ +#define AR_A 0x0001 /* position-after */ +#define AR_B 0x0002 /* position-before */ +#define AR_C 0x0004 /* creating new archive */ +#define AR_CC 0x0008 /* do not overwrite when extracting */ +#define AR_J 0x0010 /* bzip2 compression */ +#define AR_O 0x0020 /* preserve original mtime when extracting */ +#define AR_S 0x0040 /* write archive symbol table */ +#define AR_SS 0x0080 /* do not write archive symbol table */ +#define AR_TR 0x0100 /* only keep first 15 chars for member name */ +#define AR_U 0x0200 /* only extract or update newer members.*/ +#define AR_V 0x0400 /* verbose mode */ +#define AR_Z 0x0800 /* gzip compression */ + +#define DEF_BLKSZ 10240 /* default block size */ + +/* + * Convenient wrapper for general libarchive error handling. + */ +#define AC(CALL) do { \ + if ((CALL)) \ + bsdar_errc(bsdar, EX_SOFTWARE, 0, "%s", \ + archive_error_string(a)); \ +} while (0) + +/* + * In-memory representation of archive member(object). + */ +struct ar_obj { + char *name; /* member name */ + void *maddr; /* mmap start address */ + uid_t uid; /* user id */ + gid_t gid; /* group id */ + mode_t md; /* octal file permissions */ + size_t size; /* member size */ + time_t mtime; /* modification time */ + int fd; /* file descriptor */ + dev_t dev; /* inode's device */ + ino_t ino; /* inode's number */ + + TAILQ_ENTRY(ar_obj) objs; +}; + +/* + * Structure encapsulates the "global" data for "ar" program. + */ +struct bsdar { + const char *filename; /* archive name. */ + const char *posarg; /* position arg for modifiers -a, -b. */ + char mode; /* program mode */ + char compression; /* compression mode */ + int options; /* command line options */ + + const char *progname; /* program name */ + int argc; + char **argv; + + /* + * Fields for the archive string table. + */ + char *as; /* buffer for archive string table. */ + size_t as_sz; /* current size of as table. */ + size_t as_cap; /* capacity of as table buffer. */ + + /* + * Fields for the archive symbol table. + */ + uint32_t s_cnt; /* current number of symbols. */ + uint32_t *s_so; /* symbol offset table. */ + size_t s_so_cap; /* capacity of so table buffer. */ + char *s_sn; /* symbol name table */ + size_t s_sn_cap; /* capacity of sn table buffer. */ + size_t s_sn_sz; /* current size of sn table. */ + /* Current member's offset (relative to the end of pseudo members.) */ + off_t rela_off; + + TAILQ_HEAD(, ar_obj) v_obj; /* object(member) list */ +}; + +void bsdar_errc(struct bsdar *, int _eval, int _code, + const char *fmt, ...); +void bsdar_warnc(struct bsdar *, int _code, const char *fmt, ...); +void ar_mode_d(struct bsdar *bsdar); +void ar_mode_m(struct bsdar *bsdar); +void ar_mode_p(struct bsdar *bsdar); +void ar_mode_r(struct bsdar *bsdar); +void ar_mode_s(struct bsdar *bsdar); +void ar_mode_t(struct bsdar *bsdar); +void ar_mode_x(struct bsdar *bsdar); diff --git a/usr.bin/ar/read.c b/usr.bin/ar/read.c new file mode 100644 index 000000000000..a9e2ed103193 --- /dev/null +++ b/usr.bin/ar/read.c @@ -0,0 +1,204 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar.h" + +static void read_archive(struct bsdar *bsdar, char mode); + +void +ar_mode_p(struct bsdar *bsdar) +{ + + read_archive(bsdar, 'p'); +} + +void +ar_mode_t(struct bsdar *bsdar) +{ + + read_archive(bsdar, 't'); +} + +void +ar_mode_x(struct bsdar *bsdar) +{ + + read_archive(bsdar, 'x'); +} + +/* + * Handle read modes: 'x', 't' and 'p'. + */ +static void +read_archive(struct bsdar *bsdar, char mode) +{ + struct archive *a; + struct archive_entry *entry; + struct stat sb; + struct tm *tp; + const char *bname; + const char *name; + mode_t md; + size_t size; + time_t mtime; + uid_t uid; + gid_t gid; + char **av; + char buf[25]; + char find; + int flags, r, i; + + if ((a = archive_read_new()) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, 0, "archive_read_new failed"); + archive_read_support_compression_all(a); + archive_read_support_format_all(a); + AC(archive_read_open_file(a, bsdar->filename, DEF_BLKSZ)); + + for (;;) { + r = archive_read_next_header(a, &entry); + if (r == ARCHIVE_WARN || r == ARCHIVE_RETRY || + r == ARCHIVE_FATAL) + bsdar_warnc(bsdar, 0, "%s", archive_error_string(a)); + if (r == ARCHIVE_EOF || r == ARCHIVE_FATAL) + break; + if (r == ARCHIVE_RETRY) { + bsdar_warnc(bsdar, 0, "Retrying..."); + continue; + } + + name = archive_entry_pathname(entry); + + /* Skip pseudo members. */ + if (strcmp(name, "/") == 0 || strcmp(name, "//") == 0) + continue; + + if (bsdar->argc > 0) { + find = 0; + for(i = 0; i < bsdar->argc; i++) { + av = &bsdar->argv[i]; + if (*av == NULL) + continue; + if ((bname = basename(*av)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, + "basename failed"); + if (strcmp(bname, name) != 0) + continue; + + *av = NULL; + find = 1; + break; + } + if (!find) + continue; + } + + if (mode == 't') { + if (bsdar->options & AR_V) { + md = archive_entry_mode(entry); + uid = archive_entry_uid(entry); + gid = archive_entry_gid(entry); + size = archive_entry_size(entry); + mtime = archive_entry_mtime(entry); + (void)strmode(md, buf); + (void)fprintf(stdout, "%s %6d/%-6d %8ju ", + buf + 1, uid, gid, (uintmax_t)size); + tp = localtime(&mtime); + (void)strftime(buf, sizeof(buf), + "%b %e %H:%M %Y", tp); + (void)fprintf(stdout, "%s %s", buf, name); + } else + (void)fprintf(stdout, "%s", name); + r = archive_read_data_skip(a); + if (r == ARCHIVE_WARN || r == ARCHIVE_RETRY || + r == ARCHIVE_FATAL) { + (void)fprintf(stdout, "\n"); + bsdar_warnc(bsdar, 0, "%s", + archive_error_string(a)); + } + + if (r == ARCHIVE_FATAL) + break; + + (void)fprintf(stdout, "\n"); + } else { + /* mode == 'x' || mode = 'p' */ + if (mode == 'p') { + if (bsdar->options & AR_V) { + (void)fprintf(stdout, "\n<%s>\n\n", + name); + fflush(stdout); + } + r = archive_read_data_into_fd(a, 1); + } else { + /* mode == 'x' */ + if (stat(name, &sb) != 0) { + if (errno != ENOENT) { + bsdar_warnc(bsdar, 0, + "stat %s failed", + bsdar->filename); + continue; + } + } else { + /* stat success, file exist */ + if (bsdar->options & AR_CC) + continue; + if (bsdar->options & AR_U && + archive_entry_mtime(entry) <= + sb.st_mtime) + continue; + } + + if (bsdar->options & AR_V) + (void)fprintf(stdout, "x - %s\n", name); + flags = 0; + if (bsdar->options & AR_O) + flags |= ARCHIVE_EXTRACT_TIME; + + r = archive_read_extract(a, entry, flags); + } + + if (r) + bsdar_warnc(bsdar, 0, "%s", + archive_error_string(a)); + } + } + AC(archive_read_close(a)); + AC(archive_read_finish(a)); +} diff --git a/usr.bin/ar/util.c b/usr.bin/ar/util.c new file mode 100644 index 000000000000..e1230e330134 --- /dev/null +++ b/usr.bin/ar/util.c @@ -0,0 +1,86 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include "ar.h" + +static void bsdar_vwarnc(struct bsdar *, int code, + const char *fmt, va_list ap); +static void bsdar_verrc(struct bsdar *bsdar, int code, + const char *fmt, va_list ap); + +static void +bsdar_vwarnc(struct bsdar *bsdar, int code, const char *fmt, va_list ap) +{ + + fprintf(stderr, "%s: warning: ", bsdar->progname); + vfprintf(stderr, fmt, ap); + if (code != 0) + fprintf(stderr, ": %s", strerror(code)); + fprintf(stderr, "\n"); +} + +void +bsdar_warnc(struct bsdar *bsdar, int code, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + bsdar_vwarnc(bsdar, code, fmt, ap); + va_end(ap); +} + +static void +bsdar_verrc(struct bsdar *bsdar, int code, const char *fmt, va_list ap) +{ + + fprintf(stderr, "%s: fatal: ", bsdar->progname); + vfprintf(stderr, fmt, ap); + if (code != 0) + fprintf(stderr, ": %s", strerror(code)); + fprintf(stderr, "\n"); +} + +void +bsdar_errc(struct bsdar *bsdar, int eval, int code, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + bsdar_verrc(bsdar, code, fmt, ap); + va_end(ap); + exit(eval); +} diff --git a/usr.bin/ar/write.c b/usr.bin/ar/write.c new file mode 100644 index 000000000000..959fb684a4b1 --- /dev/null +++ b/usr.bin/ar/write.c @@ -0,0 +1,784 @@ +/*- + * Copyright (c) 2007 Kai Wang + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar.h" + +#define _ARMAG_LEN 8 /* length of ar magic string */ +#define _ARHDR_LEN 60 /* length of ar header */ +#define _INIT_AS_CAP 128 /* initial archive string table size */ +#define _INIT_SYMOFF_CAP (256*(sizeof(uint32_t))) /* initial so table size */ +#define _INIT_SYMNAME_CAP 1024 /* initial sn table size */ +#define _MAXNAMELEN_SVR4 15 /* max member name length in svr4 variant */ +#define _TRUNCATE_LEN 15 /* number of bytes to keep for member name */ + +static void add_to_ar_str_table(struct bsdar *bsdar, const char *name); +static void add_to_ar_sym_table(struct bsdar *bsdar, const char *name); +static struct ar_obj *create_obj_from_file(struct bsdar *bsdar, + const char *name, time_t mtime); +static void create_symtab_entry(struct bsdar *bsdar, void *maddr, + size_t size); +static void insert_obj(struct bsdar *bsdar, struct ar_obj *obj, + struct ar_obj *pos); +static void write_archive(struct bsdar *bsdar, char mode); +static void write_cleanup(struct bsdar *bsdar); +static void write_data(struct bsdar *bsdar, struct archive *a, + const void *buf, size_t s); +static void write_objs(struct bsdar *bsdar); + +void +ar_mode_d(struct bsdar *bsdar) +{ + + write_archive(bsdar, 'd'); +} + +void +ar_mode_m(struct bsdar *bsdar) +{ + + write_archive(bsdar, 'm'); +} + +void +ar_mode_r(struct bsdar *bsdar) +{ + + write_archive(bsdar, 'r'); +} + +void +ar_mode_s(struct bsdar *bsdar) +{ + + write_archive(bsdar, 's'); +} + +/* + * Create object from file, return created obj upon success, or NULL + * when an error occurs or the member is not newer than existing + * one while -u is specifed. + */ +static struct ar_obj * +create_obj_from_file(struct bsdar *bsdar, const char *name, time_t mtime) +{ + struct ar_obj *obj; + struct stat sb; + const char *bname; + + if (name == NULL) + return(NULL); + + obj = malloc(sizeof(struct ar_obj)); + if (obj == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "malloc failed"); + if ((obj->fd = open(name, O_RDONLY, 0)) < 0) { + bsdar_warnc(bsdar, errno, "can't open file: %s", name); + free(obj); + return(NULL); + } + + if ((bname = basename(name)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "basename failed"); + if (bsdar->options & AR_TR && strlen(bname) > _TRUNCATE_LEN) { + if ((obj->name = malloc(_TRUNCATE_LEN + 1)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "malloc failed"); + (void)strncpy(obj->name, bname, _TRUNCATE_LEN); + obj->name[_TRUNCATE_LEN] = '\0'; + } else + if ((obj->name = strdup(bname)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "strdup failed"); + + if (fstat(obj->fd, &sb) < 0) { + bsdar_warnc(bsdar, errno, "can't fstat file: %s", obj->name); + goto giveup; + } + if (!S_ISREG(sb.st_mode)) { + bsdar_warnc(bsdar, 0, "%s is not an ordinary file", obj->name); + goto giveup; + } + + /* + * When option '-u' is specified and member is not newer than the + * existing one, the replace will not happen. While if mtime == 0, + * which indicates that this is to "replace a none exist member", + * the replace will proceed regardless of '-u'. + */ + if (mtime != 0 && bsdar->options & AR_U && sb.st_mtime <= mtime) + goto giveup; + + obj->uid = sb.st_uid; + obj->gid = sb.st_gid; + obj->md = sb.st_mode; + obj->size = sb.st_size; + obj->mtime = sb.st_mtime; + obj->dev = sb.st_dev; + obj->ino = sb.st_ino; + if ((obj->maddr = mmap(NULL, obj->size, PROT_READ, + MAP_PRIVATE, obj->fd, (off_t)0)) == MAP_FAILED) { + bsdar_warnc(bsdar, errno, "can't mmap file: %s", obj->name); + goto giveup; + } + if (close(obj->fd) < 0) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "close failed: %s", + obj->name); + + return(obj); + +giveup: + if (close(obj->fd) < 0) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "close failed: %s", + obj->name); + free(obj->name); + free(obj); + return(NULL); +} + +/* + * Insert obj to the tail, or before/after the pos obj. + */ +static void +insert_obj(struct bsdar *bsdar, struct ar_obj *obj, struct ar_obj *pos) +{ + if (obj == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, 0, "try to insert a null obj"); + + if (pos == NULL || obj == pos) + /* + * If the object to move happens to be the posistion obj, + * or if there is not a pos obj, move it to tail. + */ + goto tail; + + if (bsdar->options & AR_B) { + TAILQ_INSERT_BEFORE(pos, obj, objs); + return; + } + if (bsdar->options & AR_A) { + TAILQ_INSERT_AFTER(&bsdar->v_obj, pos, obj, objs); + return; + } + +tail: + TAILQ_INSERT_TAIL(&bsdar->v_obj, obj, objs); + +} + +/* + * Determine the constitution of resulting archive. + */ +static void +write_archive(struct bsdar *bsdar, char mode) +{ + struct archive *a; + struct archive_entry *entry; + struct ar_obj *nobj, *obj, *obj_temp, *pos; + struct stat sb; + const char *name; + const char *bname; + char *buff; + char **av; + size_t size; + int i, r; + + TAILQ_INIT(&bsdar->v_obj); + nobj = NULL; + pos = NULL; + memset(&sb, 0, sizeof(sb)); + + /* By default, no compression is assumed. */ + bsdar->compression = ARCHIVE_COMPRESSION_NONE; + + /* + * Test if the specified archive exists, to figure out + * whether we are creating one here. + */ + if (stat(bsdar->filename, &sb) != 0) { + if (errno != ENOENT) { + bsdar_warnc(bsdar, 0, "stat %s failed", + bsdar->filename); + return; + } + + /* We do not create archive in mode 'd', 'm' and 's'. */ + if (mode != 'r') { + bsdar_warnc(bsdar, 0, "%s: no such file", + bsdar->filename); + return; + } + + /* Issue a warning if -c is not specified when creating. */ + if (!(bsdar->options & AR_C)) + bsdar_warnc(bsdar, 0, "creating %s", bsdar->filename); + goto new_archive; + } + + /* + * First read members from existing archive. + */ + if ((a = archive_read_new()) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, 0, "archive_read_new failed"); + archive_read_support_compression_all(a); + archive_read_support_format_ar(a); + AC(archive_read_open_filename(a, bsdar->filename, DEF_BLKSZ)); + for (;;) { + r = archive_read_next_header(a, &entry); + if (r == ARCHIVE_FATAL) + bsdar_errc(bsdar, EX_DATAERR, 0, "%s", + archive_error_string(a)); + if (r == ARCHIVE_EOF) + break; + if (r == ARCHIVE_WARN || r == ARCHIVE_RETRY) + bsdar_warnc(bsdar, 0, "%s", archive_error_string(a)); + if (r == ARCHIVE_RETRY) { + bsdar_warnc(bsdar, 0, "Retrying..."); + continue; + } + + /* + * Remember the compression mode of existing archive. + * If neither -j nor -z is specified, this mode will + * be used for resulting archive. + */ + bsdar->compression = archive_compression(a); + + name = archive_entry_pathname(entry); + + /* + * skip pseudo members. + */ + if (strcmp(name, "/") == 0 || strcmp(name, "//") == 0) + continue; + + size = archive_entry_size(entry); + + if ((buff = malloc(size)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "malloc failed"); + if (archive_read_data(a, buff, size) != (ssize_t)size) { + bsdar_warnc(bsdar, 0, "%s", archive_error_string(a)); + free(buff); + continue; + } + obj = malloc(sizeof(struct ar_obj)); + if (obj == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "malloc failed"); + obj->maddr = buff; + if ((obj->name = strdup(name)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "strdup failed"); + obj->size = size; + obj->uid = archive_entry_uid(entry); + obj->gid = archive_entry_gid(entry); + obj->md = archive_entry_mode(entry); + obj->mtime = archive_entry_mtime(entry); + obj->dev = 0; + obj->ino = 0; + + /* + * Objects from archive have obj->fd set to -1, + * for the ease of cleaning up. + */ + obj->fd = -1; + TAILQ_INSERT_TAIL(&bsdar->v_obj, obj, objs); + } + AC(archive_read_close(a)); + AC(archive_read_finish(a)); + + /* + * For mode 's', no member will be moved, deleted or replaced. + */ + if (mode == 's') + goto write_objs; + + /* + * Try to find the position member specified by user. + */ + if (bsdar->options & AR_A || bsdar->options & AR_B) { + TAILQ_FOREACH(obj, &bsdar->v_obj, objs) { + if (strcmp(obj->name, bsdar->posarg) == 0) { + pos = obj; + break; + } + } + + /* + * If can't find `pos' specified by user, + * sliently insert objects at tail. + */ + if (pos == NULL) + bsdar->options &= ~(AR_A | AR_B); + } + + for (i = 0; i < bsdar->argc; i++) { + av = &bsdar->argv[i]; + + TAILQ_FOREACH_SAFE(obj, &bsdar->v_obj, objs, obj_temp) { + if ((bname = basename(*av)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, + "basename failed"); + if (bsdar->options & AR_TR) { + if (strncmp(bname, obj->name, _TRUNCATE_LEN)) + continue; + } else + if (strcmp(bname, obj->name) != 0) + continue; + + if (mode == 'r') { + /* + * if the new member is not qualified + * to replace the old one, skip it. + */ + nobj = create_obj_from_file(bsdar, *av, + obj->mtime); + if (nobj == NULL) + goto skip_obj; + } + + if (bsdar->options & AR_V) + (void)fprintf(stdout, "%c - %s\n", mode, + *av); + + TAILQ_REMOVE(&bsdar->v_obj, obj, objs); + if (mode == 'd' || mode == 'r') { + free(obj->maddr); + free(obj->name); + free(obj); + } + + if (mode == 'm') + insert_obj(bsdar, obj, pos); + if (mode == 'r') + insert_obj(bsdar, nobj, pos); + + skip_obj: + *av = NULL; + break; + } + + } + +new_archive: + /* + * When operating in mode 'r', directly add those user specified + * objects which do not exist in current archive. + */ + for (i = 0; i < bsdar->argc; i++) { + av = &bsdar->argv[i]; + if (*av != NULL && mode == 'r') { + nobj = create_obj_from_file(bsdar, *av, 0); + if (nobj != NULL) + insert_obj(bsdar, nobj, pos); + if (bsdar->options & AR_V && nobj != NULL) + (void)fprintf(stdout, "a - %s\n", *av); + *av = NULL; + } + } + +write_objs: + write_objs(bsdar); + write_cleanup(bsdar); +} + +/* + * Memory cleaning up. + */ +static void +write_cleanup(struct bsdar *bsdar) +{ + struct ar_obj *obj, *obj_temp; + + TAILQ_FOREACH_SAFE(obj, &bsdar->v_obj, objs, obj_temp) { + free(obj->name); + if (obj->fd == -1) + free(obj->maddr); + else + if (munmap(obj->maddr, obj->size)) + bsdar_warnc(bsdar, errno, + "can't munmap file: %s", obj->name); + TAILQ_REMOVE(&bsdar->v_obj, obj, objs); + free(obj); + } + + free(bsdar->as); + free(bsdar->s_so); + free(bsdar->s_sn); + bsdar->as = NULL; + bsdar->s_so = NULL; + bsdar->s_sn = NULL; +} + +/* + * Wrapper for archive_write_data(). + */ +static void +write_data(struct bsdar *bsdar, struct archive *a, const void *buf, size_t s) +{ + if (archive_write_data(a, buf, s) != (ssize_t)s) + bsdar_errc(bsdar, EX_SOFTWARE, 0, "%s", + archive_error_string(a)); +} + +/* + * Write the resulting archive members. + */ +static void +write_objs(struct bsdar *bsdar) +{ + struct ar_obj *obj; + struct archive *a; + struct archive_entry *entry; + size_t s_sz; /* size of archive symbol table. */ + size_t pm_sz; /* size of pseudo members */ + int i, nr; + + if (elf_version(EV_CURRENT) == EV_NONE) + bsdar_errc(bsdar, EX_SOFTWARE, 0, + "ELF library initialization failed: %s", elf_errmsg(-1)); + + bsdar->rela_off = 0; + + /* Create archive symbol table and archive string table, if need. */ + TAILQ_FOREACH(obj, &bsdar->v_obj, objs) { + if (!(bsdar->options & AR_SS)) + create_symtab_entry(bsdar, obj->maddr, obj->size); + if (strlen(obj->name) > _MAXNAMELEN_SVR4) + add_to_ar_str_table(bsdar, obj->name); + bsdar->rela_off += _ARHDR_LEN + obj->size + obj->size % 2; + } + + /* + * Pad the symbol name string table. It is treated specially because + * symbol name table should be padded by a '\0', not the common '\n' + * for other members. The size of sn table includes the pad bit. + */ + if (bsdar->s_cnt != 0 && bsdar->s_sn_sz % 2 != 0) + bsdar->s_sn[bsdar->s_sn_sz++] = '\0'; + + /* + * Archive string table is padded by a "\n" as the normal members. + * The differece is that the size of archive string table counts + * in the pad bit, while normal members' size fileds do not. + */ + if (bsdar->as != NULL && bsdar->as_sz % 2 != 0) + bsdar->as[bsdar->as_sz++] = '\n'; + + /* + * If there is a symbol table, calculate the size of pseudo members, + * covert previously stored relative offsets to absolute ones, and + * then make them Big Endian. + * + * absolute_offset = htobe32(relative_offset + size_of_pseudo_members) + */ + + if (bsdar->s_cnt != 0) { + s_sz = (bsdar->s_cnt + 1) * sizeof(uint32_t) + bsdar->s_sn_sz; + pm_sz = _ARMAG_LEN + (_ARHDR_LEN + s_sz); + if (bsdar->as != NULL) + pm_sz += _ARHDR_LEN + bsdar->as_sz; + for (i = 0; (size_t)i < bsdar->s_cnt; i++) + *(bsdar->s_so + i) = htobe32(*(bsdar->s_so + i) + + pm_sz); + } + + if ((a = archive_write_new()) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, 0, "archive_write_new failed"); + + archive_write_set_format_ar_svr4(a); + + /* The compression mode of the existing archive is used + * for the result archive or if creating a new archive, we + * do not compress archive by default. This default behavior can + * be overrided by compression mode specified explicitly + * through command line option `-j' or `-z'. + */ + if (bsdar->options & AR_J) + bsdar->compression = ARCHIVE_COMPRESSION_BZIP2; + if (bsdar->options & AR_Z) + bsdar->compression = ARCHIVE_COMPRESSION_GZIP; + if (bsdar->compression == ARCHIVE_COMPRESSION_BZIP2) + archive_write_set_compression_bzip2(a); + else if (bsdar->compression == ARCHIVE_COMPRESSION_GZIP) + archive_write_set_compression_gzip(a); + else + archive_write_set_compression_none(a); + + AC(archive_write_open_filename(a, bsdar->filename)); + + /* + * write the archive symbol table, if there is one. + * If options -s is explicitly specified or we are invoked + * as ranlib, write the symbol table even if it is empty. + */ + if ((bsdar->s_cnt != 0 && !(bsdar->options & AR_SS)) || + bsdar->options & AR_S) { + entry = archive_entry_new(); + archive_entry_copy_pathname(entry, "/"); + archive_entry_set_mtime(entry, time(NULL), 0); + archive_entry_set_size(entry, (bsdar->s_cnt + 1) * + sizeof(uint32_t) + bsdar->s_sn_sz); + AC(archive_write_header(a, entry)); + nr = htobe32(bsdar->s_cnt); + write_data(bsdar, a, &nr, sizeof(uint32_t)); + write_data(bsdar, a, bsdar->s_so, sizeof(uint32_t) * + bsdar->s_cnt); + write_data(bsdar, a, bsdar->s_sn, bsdar->s_sn_sz); + archive_entry_free(entry); + } + + /* write the archive string table, if any. */ + if (bsdar->as != NULL) { + entry = archive_entry_new(); + archive_entry_copy_pathname(entry, "//"); + archive_entry_set_size(entry, bsdar->as_sz); + AC(archive_write_header(a, entry)); + write_data(bsdar, a, bsdar->as, bsdar->as_sz); + archive_entry_free(entry); + } + + /* write normal members. */ + TAILQ_FOREACH(obj, &bsdar->v_obj, objs) { + entry = archive_entry_new(); + archive_entry_copy_pathname(entry, obj->name); + archive_entry_set_uid(entry, obj->uid); + archive_entry_set_gid(entry, obj->gid); + archive_entry_set_mode(entry, obj->md); + archive_entry_set_size(entry, obj->size); + archive_entry_set_mtime(entry, obj->mtime, 0); + archive_entry_set_dev(entry, obj->dev); + archive_entry_set_ino(entry, obj->ino); + archive_entry_set_filetype(entry, AE_IFREG); + AC(archive_write_header(a, entry)); + write_data(bsdar, a, obj->maddr, obj->size); + archive_entry_free(entry); + } + + AC(archive_write_close(a)); + AC(archive_write_finish(a)); +} + +/* + * Extract global symbols from ELF binary members. + */ +static void +create_symtab_entry(struct bsdar *bsdar, void *maddr, size_t size) +{ + Elf *e; + Elf_Scn *scn; + GElf_Shdr shdr; + GElf_Sym sym; + Elf_Data *data; + char *name; + size_t n, shstrndx; + int elferr, tabndx, len, i; + + if ((e = elf_memory(maddr, size)) == NULL) { + bsdar_warnc(bsdar, 0, "elf_memory() failed: %s", + elf_errmsg(-1)); + return; + } + if (elf_kind(e) != ELF_K_ELF) { + /* Sliently ignore non-elf member. */ + elf_end(e); + return; + } + if (elf_getshstrndx(e, &shstrndx) == 0) { + bsdar_warnc(bsdar, EX_SOFTWARE, 0, "elf_getshstrndx failed: %s", + elf_errmsg(-1)); + elf_end(e); + return; + } + + tabndx = -1; + scn = NULL; + while ((scn = elf_nextscn(e, scn)) != NULL) { + if (gelf_getshdr(scn, &shdr) != &shdr) { + bsdar_warnc(bsdar, 0, + "elf_getshdr failed: %s", elf_errmsg(-1)); + continue; + } + if ((name = elf_strptr(e, shstrndx, shdr.sh_name)) == NULL) { + bsdar_warnc(bsdar, 0, + "elf_strptr failed: %s", elf_errmsg(-1)); + continue; + } + if (strcmp(name, ".strtab") == 0) { + tabndx = elf_ndxscn(scn); + break; + } + } + elferr = elf_errno(); + if (elferr != 0) + bsdar_warnc(bsdar, 0, "elf_nextscn failed: %s", + elf_errmsg(elferr)); + if (tabndx == -1) { + bsdar_warnc(bsdar, 0, "can't find .strtab section"); + elf_end(e); + return; + } + + scn = NULL; + while ((scn = elf_nextscn(e, scn)) != NULL) { + if (gelf_getshdr(scn, &shdr) != &shdr) { + bsdar_warnc(bsdar, EX_SOFTWARE, 0, + "elf_getshdr failed: %s", elf_errmsg(-1)); + continue; + } + if (shdr.sh_type != SHT_SYMTAB) + continue; + + data = NULL; + n = 0; + while (n < shdr.sh_size && + (data = elf_getdata(scn, data)) != NULL) { + len = data->d_size / shdr.sh_entsize; + for (i = 0; i < len; i++) { + if (gelf_getsym(data, i, &sym) != &sym) { + bsdar_warnc(bsdar, EX_SOFTWARE, 0, + "gelf_getsym failed: %s", + elf_errmsg(-1)); + continue; + } + + /* keep only global or weak symbols */ + if (GELF_ST_BIND(sym.st_info) != STB_GLOBAL && + GELF_ST_BIND(sym.st_info) != STB_WEAK) + continue; + + /* keep only defined symbols */ + if (sym.st_shndx == SHN_UNDEF) + continue; + + if ((name = elf_strptr(e, tabndx, + sym.st_name)) == NULL) { + bsdar_warnc(bsdar, EX_SOFTWARE, 0, + "elf_strptr failed: %s", + elf_errmsg(-1)); + continue; + } + + add_to_ar_sym_table(bsdar, name); + } + } + } + elferr = elf_errno(); + if (elferr != 0) + bsdar_warnc(bsdar, EX_SOFTWARE, 0, "elf_nextscn failed: %s", + elf_errmsg(elferr)); + + elf_end(e); +} + +/* + * Append to the archive string table buffer. + */ +static void +add_to_ar_str_table(struct bsdar *bsdar, const char *name) +{ + + if (bsdar->as == NULL) { + bsdar->as_cap = _INIT_AS_CAP; + bsdar->as_sz = 0; + if ((bsdar->as = malloc(bsdar->as_cap)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "malloc failed"); + } + + /* + * The space required for holding one member name in as table includes: + * strlen(name) + (1 for '/') + (1 for '\n') + (possibly 1 for padding). + */ + if (bsdar->as_sz + strlen(name) + 3 > bsdar->as_cap) { + bsdar->as_cap *= 2; + bsdar->as = realloc(bsdar->as, bsdar->as_cap); + if (bsdar->as == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "realloc failed"); + } + strncpy(&bsdar->as[bsdar->as_sz], name, strlen(name)); + bsdar->as_sz += strlen(name); + bsdar->as[bsdar->as_sz++] = '/'; + bsdar->as[bsdar->as_sz++] = '\n'; +} + +/* + * Append to the archive symbol table buffer. + */ +static void +add_to_ar_sym_table(struct bsdar *bsdar, const char *name) +{ + + if (bsdar->s_so == NULL) { + if ((bsdar->s_so = malloc(_INIT_SYMOFF_CAP)) == + NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "malloc failed"); + bsdar->s_so_cap = _INIT_SYMOFF_CAP; + bsdar->s_cnt = 0; + } + + if (bsdar->s_sn == NULL) { + if ((bsdar->s_sn = malloc(_INIT_SYMNAME_CAP)) == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "malloc failed"); + bsdar->s_sn_cap = _INIT_SYMNAME_CAP; + bsdar->s_sn_sz = 0; + } + + if (bsdar->s_cnt * sizeof(uint32_t) >= bsdar->s_so_cap) { + bsdar->s_so_cap *= 2; + bsdar->s_so = realloc(bsdar->s_so, bsdar->s_so_cap); + if (bsdar->s_so == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "realloc failed"); + } + bsdar->s_so[bsdar->s_cnt] = bsdar->rela_off; + bsdar->s_cnt++; + + /* + * The space required for holding one symbol name in sn table includes: + * strlen(name) + (1 for '\n') + (possibly 1 for padding). + */ + if (bsdar->s_sn_sz + strlen(name) + 2 > bsdar->s_sn_cap) { + bsdar->s_sn_cap *= 2; + bsdar->s_sn = realloc(bsdar->s_sn, bsdar->s_sn_cap); + if (bsdar->s_sn == NULL) + bsdar_errc(bsdar, EX_SOFTWARE, errno, "realloc failed"); + } + strncpy(&bsdar->s_sn[bsdar->s_sn_sz], name, strlen(name)); + bsdar->s_sn_sz += strlen(name); + bsdar->s_sn[bsdar->s_sn_sz++] = '\0'; +}