freebsd-dev/lib/libarchive/archive_entry.c
Tim Kientzle 0e35a97bf9 archive_entry_set_link is supposed to update whichever link field
(symlink or hardlink) is already set.  Instead, it was always setting
the hardlink field.  In particular, this caused GNU tar format long
symlinks to be interpreted as hardlinks.

Thanks to: Brooks Davis
MFC after: 7 days
2005-05-08 19:10:41 +00:00

1535 lines
38 KiB
C

/*-
* Copyright (c) 2003-2004 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 "archive_platform.h"
__FBSDID("$FreeBSD$");
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_EXT2FS_EXT2_FS_H
#include <ext2fs/ext2_fs.h> /* for Linux file flags */
#endif
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "archive.h"
#include "archive_entry.h"
#undef max
#define max(a, b) ((a)>(b)?(a):(b))
/*
* Handle wide character (i.e., Unicode) and non-wide character
* strings transparently.
*
*/
struct aes {
const char *aes_mbs;
char *aes_mbs_alloc;
const wchar_t *aes_wcs;
wchar_t *aes_wcs_alloc;
};
struct ae_acl {
struct ae_acl *next;
int type; /* E.g., access or default */
int tag; /* E.g., user/group/other/mask */
int permset; /* r/w/x bits */
int id; /* uid/gid for user/group */
struct aes name; /* uname/gname */
};
static void aes_clean(struct aes *);
static void aes_copy(struct aes *dest, struct aes *src);
static const char * aes_get_mbs(struct aes *);
static const wchar_t * aes_get_wcs(struct aes *);
static void aes_set_mbs(struct aes *, const char *mbs);
static void aes_copy_mbs(struct aes *, const char *mbs);
/* static void aes_set_wcs(struct aes *, const wchar_t *wcs); */
static void aes_copy_wcs(struct aes *, const wchar_t *wcs);
static char * ae_fflagstostr(unsigned long bitset, unsigned long bitclear);
static const wchar_t *ae_wcstofflags(const wchar_t *stringp,
unsigned long *setp, unsigned long *clrp);
static void append_entry_w(wchar_t **wp, const wchar_t *prefix, int tag,
const wchar_t *wname, int perm, int id);
static void append_id_w(wchar_t **wp, int id);
static int acl_special(struct archive_entry *entry,
int type, int permset, int tag);
static struct ae_acl *acl_new_entry(struct archive_entry *entry,
int type, int permset, int tag, int id);
static void next_field_w(const wchar_t **wp, const wchar_t **start,
const wchar_t **end, wchar_t *sep);
static int prefix_w(const wchar_t *start, const wchar_t *end,
const wchar_t *test);
/*
* Description of an archive entry.
*
* Basically, this is a "struct stat" with a few text fields added in.
*
* TODO: Add "comment", "charset", and possibly other entries
* that are supported by "pax interchange" format. However, GNU, ustar,
* cpio, and other variants don't support these features, so they're not an
* excruciatingly high priority right now.
*
* TODO: "pax interchange" format allows essentially arbitrary
* key/value attributes to be attached to any entry. Supporting
* such extensions may make this library useful for special
* applications (e.g., a package manager could attach special
* package-management attributes to each entry). There are tricky
* API issues involved, so this is not going to happen until
* there's a real demand for it.
*
* TODO: Design a good API for handling sparse files.
*/
struct archive_entry {
/*
* Note that ae_stat.st_mode & S_IFMT can be 0!
*
* This occurs when the actual file type of the object is not
* in the archive. For example, 'tar' archives store
* hardlinks without marking the type of the underlying
* object.
*/
struct stat ae_stat;
/*
* Use aes here so that we get transparent mbs<->wcs conversions.
*/
struct aes ae_fflags_text; /* Text fflags per fflagstostr(3) */
unsigned long ae_fflags_set; /* Bitmap fflags */
unsigned long ae_fflags_clear;
struct aes ae_gname; /* Name of owning group */
struct aes ae_hardlink; /* Name of target for hardlink */
struct aes ae_pathname; /* Name of entry */
struct aes ae_symlink; /* symlink contents */
struct aes ae_uname; /* Name of owner */
struct ae_acl *acl_head;
struct ae_acl *acl_p;
int acl_state; /* See acl_next for details. */
wchar_t *acl_text_w;
};
static void
aes_clean(struct aes *aes)
{
if (aes->aes_mbs_alloc) {
free(aes->aes_mbs_alloc);
aes->aes_mbs_alloc = NULL;
}
if (aes->aes_wcs_alloc) {
free(aes->aes_wcs_alloc);
aes->aes_wcs_alloc = NULL;
}
memset(aes, 0, sizeof(*aes));
}
static void
aes_copy(struct aes *dest, struct aes *src)
{
*dest = *src;
if (src->aes_mbs != NULL) {
dest->aes_mbs_alloc = strdup(src->aes_mbs);
dest->aes_mbs = dest->aes_mbs_alloc;
}
if (src->aes_wcs != NULL) {
dest->aes_wcs_alloc = malloc((wcslen(src->aes_wcs) + 1)
* sizeof(wchar_t));
dest->aes_wcs = dest->aes_wcs_alloc;
wcscpy(dest->aes_wcs_alloc, src->aes_wcs);
}
}
static const char *
aes_get_mbs(struct aes *aes)
{
if (aes->aes_mbs == NULL && aes->aes_wcs != NULL) {
/*
* XXX Need to estimate the number of byte in the
* multi-byte form. Assume that, on average, wcs
* chars encode to no more than 3 bytes. There must
* be a better way... XXX
*/
int mbs_length = wcslen(aes->aes_wcs) * 3 + 64;
aes->aes_mbs_alloc = malloc(mbs_length);
aes->aes_mbs = aes->aes_mbs_alloc;
wcstombs(aes->aes_mbs_alloc, aes->aes_wcs, mbs_length - 1);
aes->aes_mbs_alloc[mbs_length - 1] = 0;
}
return (aes->aes_mbs);
}
static const wchar_t *
aes_get_wcs(struct aes *aes)
{
if (aes->aes_wcs == NULL && aes->aes_mbs != NULL) {
/*
* No single byte will be more than one wide character,
* so this length estimate will always be big enough.
*/
int wcs_length = strlen(aes->aes_mbs);
aes->aes_wcs_alloc
= malloc((wcs_length + 1) * sizeof(wchar_t));
aes->aes_wcs = aes->aes_wcs_alloc;
mbstowcs(aes->aes_wcs_alloc, aes->aes_mbs, wcs_length);
aes->aes_wcs_alloc[wcs_length] = 0;
}
return (aes->aes_wcs);
}
static void
aes_set_mbs(struct aes *aes, const char *mbs)
{
if (aes->aes_mbs_alloc) {
free(aes->aes_mbs_alloc);
aes->aes_mbs_alloc = NULL;
}
if (aes->aes_wcs_alloc) {
free(aes->aes_wcs_alloc);
aes->aes_wcs_alloc = NULL;
}
aes->aes_mbs = mbs;
aes->aes_wcs = NULL;
}
static void
aes_copy_mbs(struct aes *aes, const char *mbs)
{
if (aes->aes_mbs_alloc) {
free(aes->aes_mbs_alloc);
aes->aes_mbs_alloc = NULL;
}
if (aes->aes_wcs_alloc) {
free(aes->aes_wcs_alloc);
aes->aes_wcs_alloc = NULL;
}
aes->aes_mbs_alloc = malloc((strlen(mbs) + 1) * sizeof(char));
strcpy(aes->aes_mbs_alloc, mbs);
aes->aes_mbs = aes->aes_mbs_alloc;
aes->aes_wcs = NULL;
}
#if 0
static void
aes_set_wcs(struct aes *aes, const wchar_t *wcs)
{
if (aes->aes_mbs_alloc) {
free(aes->aes_mbs_alloc);
aes->aes_mbs_alloc = NULL;
}
if (aes->aes_wcs_alloc) {
free(aes->aes_wcs_alloc);
aes->aes_wcs_alloc = NULL;
}
aes->aes_mbs = NULL;
aes->aes_wcs = wcs;
}
#endif
static void
aes_copy_wcs(struct aes *aes, const wchar_t *wcs)
{
if (aes->aes_mbs_alloc) {
free(aes->aes_mbs_alloc);
aes->aes_mbs_alloc = NULL;
}
if (aes->aes_wcs_alloc) {
free(aes->aes_wcs_alloc);
aes->aes_wcs_alloc = NULL;
}
aes->aes_mbs = NULL;
aes->aes_wcs_alloc = malloc((wcslen(wcs) + 1) * sizeof(wchar_t));
wcscpy(aes->aes_wcs_alloc, wcs);
aes->aes_wcs = aes->aes_wcs_alloc;
}
struct archive_entry *
archive_entry_clear(struct archive_entry *entry)
{
aes_clean(&entry->ae_fflags_text);
aes_clean(&entry->ae_gname);
aes_clean(&entry->ae_hardlink);
aes_clean(&entry->ae_pathname);
aes_clean(&entry->ae_symlink);
aes_clean(&entry->ae_uname);
archive_entry_acl_clear(entry);
memset(entry, 0, sizeof(*entry));
return entry;
}
struct archive_entry *
archive_entry_clone(struct archive_entry *entry)
{
struct archive_entry *entry2;
/* Allocate new structure and copy over all of the fields. */
entry2 = malloc(sizeof(*entry2));
if(entry2 == NULL)
return (NULL);
memset(entry2, 0, sizeof(*entry2));
entry2->ae_stat = entry->ae_stat;
entry2->ae_fflags_set = entry->ae_fflags_set;
entry2->ae_fflags_clear = entry->ae_fflags_clear;
aes_copy(&entry2->ae_fflags_text, &entry->ae_fflags_text);
aes_copy(&entry2->ae_gname, &entry->ae_gname);
aes_copy(&entry2->ae_hardlink, &entry->ae_hardlink);
aes_copy(&entry2->ae_pathname, &entry->ae_pathname);
aes_copy(&entry2->ae_symlink, &entry->ae_symlink);
aes_copy(&entry2->ae_uname, &entry->ae_uname);
/* XXX TODO: Copy ACL data over as well. XXX */
return (entry2);
}
void
archive_entry_free(struct archive_entry *entry)
{
archive_entry_clear(entry);
free(entry);
}
struct archive_entry *
archive_entry_new(void)
{
struct archive_entry *entry;
entry = malloc(sizeof(*entry));
if(entry == NULL)
return (NULL);
memset(entry, 0, sizeof(*entry));
return (entry);
}
/*
* Functions for reading fields from an archive_entry.
*/
time_t
archive_entry_atime(struct archive_entry *entry)
{
return (entry->ae_stat.st_atime);
}
long
archive_entry_atime_nsec(struct archive_entry *entry)
{
(void)entry; /* entry can be unused here. */
return (ARCHIVE_STAT_ATIME_NANOS(&entry->ae_stat));
}
dev_t
archive_entry_dev(struct archive_entry *entry)
{
return (entry->ae_stat.st_dev);
}
void
archive_entry_fflags(struct archive_entry *entry,
unsigned long *set, unsigned long *clear)
{
*set = entry->ae_fflags_set;
*clear = entry->ae_fflags_clear;
}
/*
* Note: if text was provided, this just returns that text. If you
* really need the text to be rebuilt in a canonical form, set the
* text, ask for the bitmaps, then set the bitmaps. (Setting the
* bitmaps clears any stored text.) This design is deliberate: if
* we're editing archives, we don't want to discard flags just because
* they aren't supported on the current system. The bitmap<->text
* conversions are platform-specific (see below).
*/
const char *
archive_entry_fflags_text(struct archive_entry *entry)
{
const char *f;
char *p;
f = aes_get_mbs(&entry->ae_fflags_text);
if (f != NULL)
return (f);
if (entry->ae_fflags_set == 0 && entry->ae_fflags_clear == 0)
return (NULL);
p = ae_fflagstostr(entry->ae_fflags_set, entry->ae_fflags_clear);
if (p == NULL)
return (NULL);
aes_copy_mbs(&entry->ae_fflags_text, p);
free(p);
f = aes_get_mbs(&entry->ae_fflags_text);
return (f);
}
gid_t
archive_entry_gid(struct archive_entry *entry)
{
return (entry->ae_stat.st_gid);
}
const char *
archive_entry_gname(struct archive_entry *entry)
{
return (aes_get_mbs(&entry->ae_gname));
}
const char *
archive_entry_hardlink(struct archive_entry *entry)
{
return (aes_get_mbs(&entry->ae_hardlink));
}
ino_t
archive_entry_ino(struct archive_entry *entry)
{
return (entry->ae_stat.st_ino);
}
mode_t
archive_entry_mode(struct archive_entry *entry)
{
return (entry->ae_stat.st_mode);
}
time_t
archive_entry_mtime(struct archive_entry *entry)
{
return (entry->ae_stat.st_mtime);
}
long
archive_entry_mtime_nsec(struct archive_entry *entry)
{
(void)entry; /* entry can be unused here. */
return (ARCHIVE_STAT_MTIME_NANOS(&entry->ae_stat));
}
const char *
archive_entry_pathname(struct archive_entry *entry)
{
return (aes_get_mbs(&entry->ae_pathname));
}
const wchar_t *
archive_entry_pathname_w(struct archive_entry *entry)
{
return (aes_get_wcs(&entry->ae_pathname));
}
dev_t
archive_entry_rdev(struct archive_entry *entry)
{
return (entry->ae_stat.st_rdev);
}
dev_t
archive_entry_rdevmajor(struct archive_entry *entry)
{
return (major(entry->ae_stat.st_rdev));
}
dev_t
archive_entry_rdevminor(struct archive_entry *entry)
{
return (minor(entry->ae_stat.st_rdev));
}
int64_t
archive_entry_size(struct archive_entry *entry)
{
return (entry->ae_stat.st_size);
}
const struct stat *
archive_entry_stat(struct archive_entry *entry)
{
return (&entry->ae_stat);
}
const char *
archive_entry_symlink(struct archive_entry *entry)
{
return (aes_get_mbs(&entry->ae_symlink));
}
uid_t
archive_entry_uid(struct archive_entry *entry)
{
return (entry->ae_stat.st_uid);
}
const char *
archive_entry_uname(struct archive_entry *entry)
{
return (aes_get_mbs(&entry->ae_uname));
}
/*
* Functions to set archive_entry properties.
*/
/*
* Note "copy" not "set" here. The "set" functions that accept a pointer
* only store the pointer; they don't copy the underlying object.
*/
void
archive_entry_copy_stat(struct archive_entry *entry, const struct stat *st)
{
entry->ae_stat = *st;
}
void
archive_entry_set_fflags(struct archive_entry *entry,
unsigned long set, unsigned long clear)
{
aes_clean(&entry->ae_fflags_text);
entry->ae_fflags_set = set;
entry->ae_fflags_clear = clear;
}
const wchar_t *
archive_entry_copy_fflags_text_w(struct archive_entry *entry,
const wchar_t *flags)
{
aes_copy_wcs(&entry->ae_fflags_text, flags);
return (ae_wcstofflags(flags,
&entry->ae_fflags_set, &entry->ae_fflags_clear));
}
void
archive_entry_set_gid(struct archive_entry *entry, gid_t g)
{
entry->ae_stat.st_gid = g;
}
void
archive_entry_set_gname(struct archive_entry *entry, const char *name)
{
aes_set_mbs(&entry->ae_gname, name);
}
void
archive_entry_copy_gname_w(struct archive_entry *entry, const wchar_t *name)
{
aes_copy_wcs(&entry->ae_gname, name);
}
void
archive_entry_set_hardlink(struct archive_entry *entry, const char *target)
{
aes_set_mbs(&entry->ae_hardlink, target);
}
void
archive_entry_copy_hardlink(struct archive_entry *entry, const char *target)
{
aes_copy_mbs(&entry->ae_hardlink, target);
}
void
archive_entry_copy_hardlink_w(struct archive_entry *entry, const wchar_t *target)
{
aes_copy_wcs(&entry->ae_hardlink, target);
}
/* Set symlink if symlink is already set, else set hardlink. */
void
archive_entry_set_link(struct archive_entry *entry, const char *target)
{
if (entry->ae_symlink.aes_mbs != NULL ||
entry->ae_symlink.aes_wcs != NULL)
aes_set_mbs(&entry->ae_symlink, target);
else
aes_set_mbs(&entry->ae_hardlink, target);
}
void
archive_entry_set_mode(struct archive_entry *entry, mode_t m)
{
entry->ae_stat.st_mode = m;
}
void
archive_entry_set_mtime(struct archive_entry *entry, time_t m, long ns)
{
entry->ae_stat.st_mtime = m;
ARCHIVE_STAT_SET_MTIME_NANOS(&entry->ae_stat, ns);
}
void
archive_entry_set_pathname(struct archive_entry *entry, const char *name)
{
aes_set_mbs(&entry->ae_pathname, name);
}
void
archive_entry_copy_pathname(struct archive_entry *entry, const char *name)
{
aes_copy_mbs(&entry->ae_pathname, name);
}
void
archive_entry_copy_pathname_w(struct archive_entry *entry, const wchar_t *name)
{
aes_copy_wcs(&entry->ae_pathname, name);
}
void
archive_entry_set_rdevmajor(struct archive_entry *entry, dev_t m)
{
dev_t d;
d = entry->ae_stat.st_rdev;
entry->ae_stat.st_rdev = makedev(major(m), minor(d));
}
void
archive_entry_set_rdevminor(struct archive_entry *entry, dev_t m)
{
dev_t d;
d = entry->ae_stat.st_rdev;
entry->ae_stat.st_rdev = makedev(major(d), minor(m));
}
void
archive_entry_set_size(struct archive_entry *entry, int64_t s)
{
entry->ae_stat.st_size = s;
}
void
archive_entry_set_symlink(struct archive_entry *entry, const char *linkname)
{
aes_set_mbs(&entry->ae_symlink, linkname);
}
void
archive_entry_copy_symlink_w(struct archive_entry *entry, const wchar_t *linkname)
{
aes_copy_wcs(&entry->ae_symlink, linkname);
}
void
archive_entry_set_uid(struct archive_entry *entry, uid_t u)
{
entry->ae_stat.st_uid = u;
}
void
archive_entry_set_uname(struct archive_entry *entry, const char *name)
{
aes_set_mbs(&entry->ae_uname, name);
}
void
archive_entry_copy_uname_w(struct archive_entry *entry, const wchar_t *name)
{
aes_copy_wcs(&entry->ae_uname, name);
}
/*
* ACL management. The following would, of course, be a lot simpler
* if: 1) the last draft of POSIX.1e were a really thorough and
* complete standard that addressed the needs of ACL archiving and 2)
* everyone followed it faithfully. Alas, neither is true, so the
* following is a lot more complex than might seem necessary to the
* uninitiated.
*/
void
archive_entry_acl_clear(struct archive_entry *entry)
{
struct ae_acl *ap;
while (entry->acl_head != NULL) {
ap = entry->acl_head->next;
aes_clean(&entry->acl_head->name);
free(entry->acl_head);
entry->acl_head = ap;
}
if (entry->acl_text_w != NULL) {
free(entry->acl_text_w);
entry->acl_text_w = NULL;
}
entry->acl_p = NULL;
entry->acl_state = 0; /* Not counting. */
}
/*
* Add a single ACL entry to the internal list of ACL data.
*/
void
archive_entry_acl_add_entry(struct archive_entry *entry,
int type, int permset, int tag, int id, const char *name)
{
struct ae_acl *ap;
if (acl_special(entry, type, permset, tag) == 0)
return;
ap = acl_new_entry(entry, type, permset, tag, id);
if (ap == NULL) {
/* XXX Error XXX */
return;
}
if (name != NULL && *name != '\0')
aes_copy_mbs(&ap->name, name);
else
aes_clean(&ap->name);
}
/*
* As above, but with a wide-character name.
*/
void
archive_entry_acl_add_entry_w(struct archive_entry *entry,
int type, int permset, int tag, int id, const wchar_t *name)
{
struct ae_acl *ap;
if (acl_special(entry, type, permset, tag) == 0)
return;
ap = acl_new_entry(entry, type, permset, tag, id);
if (ap == NULL) {
/* XXX Error XXX */
return;
}
if (name != NULL && *name != L'\0')
aes_copy_wcs(&ap->name, name);
else
aes_clean(&ap->name);
}
/*
* If this ACL entry is part of the standard POSIX permissions set,
* store the permissions in the stat structure and return zero.
*/
static int
acl_special(struct archive_entry *entry, int type, int permset, int tag)
{
if (type == ARCHIVE_ENTRY_ACL_TYPE_ACCESS) {
switch (tag) {
case ARCHIVE_ENTRY_ACL_USER_OBJ:
entry->ae_stat.st_mode &= ~0700;
entry->ae_stat.st_mode |= (permset & 7) << 6;
return (0);
case ARCHIVE_ENTRY_ACL_GROUP_OBJ:
entry->ae_stat.st_mode &= ~0070;
entry->ae_stat.st_mode |= (permset & 7) << 3;
return (0);
case ARCHIVE_ENTRY_ACL_OTHER:
entry->ae_stat.st_mode &= ~0007;
entry->ae_stat.st_mode |= permset & 7;
return (0);
}
}
return (1);
}
/*
* Allocate and populate a new ACL entry with everything but the
* name.
*/
static struct ae_acl *
acl_new_entry(struct archive_entry *entry,
int type, int permset, int tag, int id)
{
struct ae_acl *ap;
if (type != ARCHIVE_ENTRY_ACL_TYPE_ACCESS &&
type != ARCHIVE_ENTRY_ACL_TYPE_DEFAULT)
return (NULL);
if (entry->acl_text_w != NULL) {
free(entry->acl_text_w);
entry->acl_text_w = NULL;
}
/* XXX TODO: More sanity-checks on the arguments XXX */
/* If there's a matching entry already in the list, overwrite it. */
for (ap = entry->acl_head; ap != NULL; ap = ap->next) {
if (ap->type == type && ap->tag == tag && ap->id == id) {
ap->permset = permset;
return (ap);
}
}
/* Add a new entry to the list. */
ap = malloc(sizeof(*ap));
memset(ap, 0, sizeof(*ap));
ap->next = entry->acl_head;
entry->acl_head = ap;
ap->type = type;
ap->tag = tag;
ap->id = id;
ap->permset = permset;
return (ap);
}
/*
* Return a count of entries matching "want_type".
*/
int
archive_entry_acl_count(struct archive_entry *entry, int want_type)
{
int count;
struct ae_acl *ap;
count = 0;
ap = entry->acl_head;
while (ap != NULL) {
if ((ap->type & want_type) != 0)
count++;
ap = ap->next;
}
if (count > 0 && ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0))
count += 3;
return (count);
}
/*
* Prepare for reading entries from the ACL data. Returns a count
* of entries matching "want_type", or zero if there are no
* non-extended ACL entries of that type.
*/
int
archive_entry_acl_reset(struct archive_entry *entry, int want_type)
{
int count, cutoff;
count = archive_entry_acl_count(entry, want_type);
/*
* If the only entries are the three standard ones,
* then don't return any ACL data. (In this case,
* client can just use chmod(2) to set permissions.)
*/
if ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0)
cutoff = 3;
else
cutoff = 0;
if (count > cutoff)
entry->acl_state = ARCHIVE_ENTRY_ACL_USER_OBJ;
else
entry->acl_state = 0;
entry->acl_p = entry->acl_head;
return (count);
}
/*
* Return the next ACL entry in the list. Fake entries for the
* standard permissions and include them in the returned list.
*/
int
archive_entry_acl_next(struct archive_entry *entry, int want_type, int *type,
int *permset, int *tag, int *id, const char **name)
{
*name = NULL;
*id = -1;
/*
* The acl_state is either zero (no entries available), -1
* (reading from list), or an entry type (retrieve that type
* from ae_stat.st_mode).
*/
if (entry->acl_state == 0)
return (ARCHIVE_WARN);
/* The first three access entries are special. */
if ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
switch (entry->acl_state) {
case ARCHIVE_ENTRY_ACL_USER_OBJ:
*permset = (entry->ae_stat.st_mode >> 6) & 7;
*type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
*tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
entry->acl_state = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
return (ARCHIVE_OK);
case ARCHIVE_ENTRY_ACL_GROUP_OBJ:
*permset = (entry->ae_stat.st_mode >> 3) & 7;
*type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
*tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
entry->acl_state = ARCHIVE_ENTRY_ACL_OTHER;
return (ARCHIVE_OK);
case ARCHIVE_ENTRY_ACL_OTHER:
*permset = entry->ae_stat.st_mode & 7;
*type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS;
*tag = ARCHIVE_ENTRY_ACL_OTHER;
entry->acl_state = -1;
entry->acl_p = entry->acl_head;
return (ARCHIVE_OK);
default:
break;
}
}
while (entry->acl_p != NULL && (entry->acl_p->type & want_type) == 0)
entry->acl_p = entry->acl_p->next;
if (entry->acl_p == NULL) {
entry->acl_state = 0;
return (ARCHIVE_WARN);
}
*type = entry->acl_p->type;
*permset = entry->acl_p->permset;
*tag = entry->acl_p->tag;
*id = entry->acl_p->id;
*name = aes_get_mbs(&entry->acl_p->name);
entry->acl_p = entry->acl_p->next;
return (ARCHIVE_OK);
}
/*
* Generate a text version of the ACL. The flags parameter controls
* the style of the generated ACL.
*/
const wchar_t *
archive_entry_acl_text_w(struct archive_entry *entry, int flags)
{
int count;
int length;
const wchar_t *wname;
const wchar_t *prefix;
wchar_t separator;
struct ae_acl *ap;
int id;
wchar_t *wp;
if (entry->acl_text_w != NULL) {
free (entry->acl_text_w);
entry->acl_text_w = NULL;
}
separator = L',';
count = 0;
length = 0;
ap = entry->acl_head;
while (ap != NULL) {
if ((ap->type & flags) != 0) {
count++;
if ((flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT) &&
(ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT))
length += 8; /* "default:" */
length += 5; /* tag name */
length += 1; /* colon */
wname = aes_get_wcs(&ap->name);
if (wname != NULL)
length += wcslen(wname);
length ++; /* colon */
length += 3; /* rwx */
length += 1; /* colon */
length += max(sizeof(uid_t),sizeof(gid_t)) * 3 + 1;
length ++; /* newline */
}
ap = ap->next;
}
if (count > 0 && ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0)) {
length += 10; /* "user::rwx\n" */
length += 11; /* "group::rwx\n" */
length += 11; /* "other::rwx\n" */
}
if (count == 0)
return (NULL);
/* Now, allocate the string and actually populate it. */
wp = entry->acl_text_w = malloc(length * sizeof(wchar_t));
count = 0;
if ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_USER_OBJ, NULL,
entry->ae_stat.st_mode & 0700, -1);
*wp++ = ',';
append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_GROUP_OBJ, NULL,
entry->ae_stat.st_mode & 0070, -1);
*wp++ = ',';
append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_OTHER, NULL,
entry->ae_stat.st_mode & 0007, -1);
count += 3;
ap = entry->acl_head;
while (ap != NULL) {
if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
wname = aes_get_wcs(&ap->name);
*wp++ = separator;
if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID)
id = ap->id;
else
id = -1;
append_entry_w(&wp, NULL, ap->tag, wname,
ap->permset, id);
count++;
}
ap = ap->next;
}
}
if ((flags & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0) {
if (flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT)
prefix = L"default:";
else
prefix = NULL;
ap = entry->acl_head;
count = 0;
while (ap != NULL) {
if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0) {
wname = aes_get_wcs(&ap->name);
if (count > 0)
*wp++ = separator;
if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID)
id = ap->id;
else
id = -1;
append_entry_w(&wp, prefix, ap->tag,
wname, ap->permset, id);
count ++;
}
ap = ap->next;
}
}
return (entry->acl_text_w);
}
static void
append_id_w(wchar_t **wp, int id)
{
if (id > 9)
append_id_w(wp, id / 10);
*(*wp)++ = L"0123456789"[id % 10];
}
static void
append_entry_w(wchar_t **wp, const wchar_t *prefix, int tag,
const wchar_t *wname, int perm, int id)
{
if (prefix != NULL) {
wcscpy(*wp, prefix);
*wp += wcslen(*wp);
}
switch (tag) {
case ARCHIVE_ENTRY_ACL_USER_OBJ:
wname = NULL;
id = -1;
/* FALL THROUGH */
case ARCHIVE_ENTRY_ACL_USER:
wcscpy(*wp, L"user");
break;
case ARCHIVE_ENTRY_ACL_GROUP_OBJ:
wname = NULL;
id = -1;
/* FALL THROUGH */
case ARCHIVE_ENTRY_ACL_GROUP:
wcscpy(*wp, L"group");
break;
case ARCHIVE_ENTRY_ACL_MASK:
wcscpy(*wp, L"mask");
wname = NULL;
id = -1;
break;
case ARCHIVE_ENTRY_ACL_OTHER:
wcscpy(*wp, L"other");
wname = NULL;
id = -1;
break;
}
*wp += wcslen(*wp);
*(*wp)++ = L':';
if (wname != NULL) {
wcscpy(*wp, wname);
*wp += wcslen(*wp);
}
*(*wp)++ = L':';
*(*wp)++ = (perm & 0444) ? L'r' : L'-';
*(*wp)++ = (perm & 0222) ? L'w' : L'-';
*(*wp)++ = (perm & 0111) ? L'x' : L'-';
if (id != -1) {
*(*wp)++ = L':';
append_id_w(wp, id);
}
**wp = L'\0';
}
/*
* Parse a textual ACL. This automatically recognizes and supports
* extensions described above. The 'type' argument is used to
* indicate the type that should be used for any entries not
* explicitly marked as "default:".
*/
int
__archive_entry_acl_parse_w(struct archive_entry *entry,
const wchar_t *text, int default_type)
{
int type, tag, permset, id;
const wchar_t *start, *end;
const wchar_t *name_start, *name_end;
wchar_t sep;
wchar_t *namebuff;
int namebuff_length;
name_start = name_end = NULL;
namebuff = NULL;
namebuff_length = 0;
while (text != NULL && *text != L'\0') {
next_field_w(&text, &start, &end, &sep);
if (sep != L':')
goto fail;
/*
* Solaris extension: "defaultuser::rwx" is the
* default ACL corresponding to "user::rwx", etc.
*/
if (end-start > 7 && wmemcmp(start, L"default", 7) == 0) {
type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
start += 7;
} else
type = default_type;
if (prefix_w(start, end, L"user")) {
next_field_w(&text, &start, &end, &sep);
if (sep != L':')
goto fail;
if (end > start) {
tag = ARCHIVE_ENTRY_ACL_USER;
name_start = start;
name_end = end;
} else
tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
} else if (prefix_w(start, end, L"group")) {
next_field_w(&text, &start, &end, &sep);
if (sep != L':')
goto fail;
if (end > start) {
tag = ARCHIVE_ENTRY_ACL_GROUP;
name_start = start;
name_end = end;
} else
tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
} else if (prefix_w(start, end, L"other")) {
next_field_w(&text, &start, &end, &sep);
if (sep != L':')
goto fail;
if (end > start)
goto fail;
tag = ARCHIVE_ENTRY_ACL_OTHER;
} else if (prefix_w(start, end, L"mask")) {
next_field_w(&text, &start, &end, &sep);
if (sep != L':')
goto fail;
if (end > start)
goto fail;
tag = ARCHIVE_ENTRY_ACL_MASK;
} else
goto fail;
next_field_w(&text, &start, &end, &sep);
permset = 0;
while (start < end) {
switch (*start++) {
case 'r': case 'R':
permset |= ARCHIVE_ENTRY_ACL_READ;
break;
case 'w': case 'W':
permset |= ARCHIVE_ENTRY_ACL_WRITE;
break;
case 'x': case 'X':
permset |= ARCHIVE_ENTRY_ACL_EXECUTE;
break;
case '-':
break;
default:
goto fail;
}
}
/*
* Support star-compatible numeric UID/GID extension.
* This extension adds a ":" followed by the numeric
* ID so that "group:groupname:rwx", for example,
* becomes "group:groupname:rwx:999", where 999 is the
* numeric GID. This extension makes it possible, for
* example, to correctly restore ACLs on a system that
* might have a damaged passwd file or be disconnected
* from a central NIS server. This extension is compatible
* with POSIX.1e draft 17.
*/
if (sep == L':' && (tag == ARCHIVE_ENTRY_ACL_USER ||
tag == ARCHIVE_ENTRY_ACL_GROUP)) {
next_field_w(&text, &start, &end, &sep);
id = 0;
while (start < end && *start >= '0' && *start <= '9') {
if (id > (INT_MAX / 10))
id = INT_MAX;
else {
id *= 10;
id += *start - '0';
start++;
}
}
} else
id = -1; /* No id specified. */
/* Skip any additional entries. */
while (sep == L':') {
next_field_w(&text, &start, &end, &sep);
}
/* Add entry to the internal list. */
if (name_end == name_start) {
archive_entry_acl_add_entry_w(entry, type, permset,
tag, id, NULL);
} else {
if (namebuff_length <= name_end - name_start) {
if (namebuff != NULL)
free(namebuff);
namebuff_length = name_end - name_start + 256;
namebuff =
malloc(namebuff_length * sizeof(wchar_t));
}
wmemcpy(namebuff, name_start, name_end - name_start);
namebuff[name_end - name_start] = L'\0';
archive_entry_acl_add_entry_w(entry, type,
permset, tag, id, namebuff);
}
}
if (namebuff != NULL)
free(namebuff);
return (ARCHIVE_OK);
fail:
if (namebuff != NULL)
free(namebuff);
return (ARCHIVE_WARN);
}
/*
* Match "[:whitespace:]*(.*)[:whitespace:]*[:,\n]". *wp is updated
* to point to just after the separator. *start points to the first
* character of the matched text and *end just after the last
* character of the matched identifier. In particular *end - *start
* is the length of the field body, not including leading or trailing
* whitespace.
*/
static void
next_field_w(const wchar_t **wp, const wchar_t **start,
const wchar_t **end, wchar_t *sep)
{
/* Skip leading whitespace to find start of field. */
while (**wp == L' ' || **wp == L'\t' || **wp == L'\n') {
(*wp)++;
}
*start = *wp;
/* Scan for the separator. */
while (**wp != L'\0' && **wp != L',' && **wp != L':' &&
**wp != L'\n') {
(*wp)++;
}
*sep = **wp;
/* Trim trailing whitespace to locate end of field. */
*end = *wp - 1;
while (**end == L' ' || **end == L'\t' || **end == L'\n') {
(*end)--;
}
(*end)++;
/* Adjust scanner location. */
if (**wp != L'\0')
(*wp)++;
}
static int
prefix_w(const wchar_t *start, const wchar_t *end, const wchar_t *test)
{
if (start == end)
return (0);
if (*start++ != *test++)
return (0);
while (start < end && *start++ == *test++)
;
if (start < end)
return (0);
return (1);
}
/*
* Following code is modified from UC Berkeley sources, and
* is subject to the following copyright notice.
*/
/*-
* Copyright (c) 1993
* The Regents of the University of California. 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.
* 4. 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.
*/
static struct flag {
const char *name;
const wchar_t *wname;
unsigned long set;
unsigned long clear;
} flags[] = {
/* Preferred (shorter) names per flag first, all prefixed by "no" */
#ifdef SF_APPEND
{ "nosappnd", L"nosappnd", SF_APPEND, 0 },
{ "nosappend", L"nosappend", SF_APPEND, 0 },
#endif
#ifdef EXT2_APPEND_FL /* 'a' */
{ "nosappnd", L"nosappnd", EXT2_APPEND_FL, 0 },
{ "nosappend", L"nosappend", EXT2_APPEND_FL, 0 },
#endif
#ifdef SF_ARCHIVED
{ "noarch", L"noarch", SF_ARCHIVED, 0 },
{ "noarchived", L"noarchived", SF_ARCHIVED, 0 },
#endif
#ifdef SF_IMMUTABLE
{ "noschg", L"noschg", SF_IMMUTABLE, 0 },
{ "noschange", L"noschange", SF_IMMUTABLE, 0 },
{ "nosimmutable", L"nosimmutable", SF_IMMUTABLE, 0 },
#endif
#ifdef EXT2_IMMUTABLE_FL /* 'i' */
{ "noschg", L"noschg", EXT2_IMMUTABLE_FL, 0 },
{ "noschange", L"noschange", EXT2_IMMUTABLE_FL, 0 },
{ "nosimmutable", L"nosimmutable", EXT2_IMMUTABLE_FL, 0 },
#endif
#ifdef SF_NOUNLINK
{ "nosunlnk", L"nosunlnk", SF_NOUNLINK, 0 },
{ "nosunlink", L"nosunlink", SF_NOUNLINK, 0 },
#endif
#ifdef SF_SNAPSHOT
{ "nosnapshot", L"nosnapshot", SF_SNAPSHOT, 0 },
#endif
#ifdef UF_APPEND
{ "nouappnd", L"nouappnd", UF_APPEND, 0 },
{ "nouappend", L"nouappend", UF_APPEND, 0 },
#endif
#ifdef UF_IMMUTABLE
{ "nouchg", L"nouchg", UF_IMMUTABLE, 0 },
{ "nouchange", L"nouchange", UF_IMMUTABLE, 0 },
{ "nouimmutable", L"nouimmutable", UF_IMMUTABLE, 0 },
#endif
#ifdef UF_NODUMP
{ "nodump", L"nodump", 0, UF_NODUMP},
#endif
#ifdef EXT2_NODUMP_FL /* 'd' */
{ "nodump", L"nodump", 0, EXT2_NODUMP_FL},
#endif
#ifdef UF_OPAQUE
{ "noopaque", L"noopaque", UF_OPAQUE, 0 },
#endif
#ifdef UF_NOUNLINK
{ "nouunlnk", L"nouunlnk", UF_NOUNLINK, 0 },
{ "nouunlink", L"nouunlink", UF_NOUNLINK, 0 },
#endif
#ifdef EXT2_COMPR_FL /* 'c' */
{ "nocompress", L"nocompress", EXT2_COMPR_FL, 0 },
#endif
#ifdef EXT2_NOATIME_FL /* 'A' */
{ "noatime", L"noatime", 0, EXT2_NOATIME_FL},
#endif
{ NULL, NULL, 0, 0 }
};
/*
* fflagstostr --
* Convert file flags to a comma-separated string. If no flags
* are set, return the empty string.
*/
char *
ae_fflagstostr(unsigned long bitset, unsigned long bitclear)
{
char *string, *dp;
const char *sp;
unsigned long bits;
struct flag *flag;
int length;
bits = bitset | bitclear;
length = 0;
for (flag = flags; flag->name != NULL; flag++)
if (bits & (flag->set | flag->clear)) {
length += strlen(flag->name) + 1;
bits &= ~(flag->set | flag->clear);
}
if (length == 0)
return (NULL);
string = malloc(length);
if (string == NULL)
return (NULL);
dp = string;
for (flag = flags; flag->name != NULL; flag++) {
if (bitset & flag->set || bitclear & flag->clear) {
sp = flag->name + 2;
} else if (bitset & flag->clear || bitclear & flag->set) {
sp = flag->name;
} else
continue;
bitset &= ~(flag->set | flag->clear);
bitclear &= ~(flag->set | flag->clear);
if (dp > string)
*dp++ = ',';
while ((*dp++ = *sp++) != '\0')
;
dp--;
}
*dp = '\0';
return (string);
}
/*
* wcstofflags --
* Take string of arguments and return file flags. This
* version works a little differently than strtofflags(3).
* In particular, it always tests every token, skipping any
* unrecognized tokens. It returns a pointer to the first
* unrecognized token, or NULL if every token was recognized.
* This version is also const-correct and does not modify the
* provided string.
*/
const wchar_t *
ae_wcstofflags(const wchar_t *s, unsigned long *setp, unsigned long *clrp)
{
const wchar_t *start, *end;
struct flag *flag;
unsigned long set, clear;
const wchar_t *failed;
set = clear = 0;
start = s;
failed = NULL;
/* Find start of first token. */
while (*start == L'\t' || *start == L' ' || *start == L',')
start++;
while (*start != L'\0') {
/* Locate end of token. */
end = start;
while (*end != L'\0' && *end != L'\t' &&
*end != L' ' && *end != L',')
end++;
for (flag = flags; flag->wname != NULL; flag++) {
if (wmemcmp(start, flag->wname, end - start) == 0) {
/* Matched "noXXXX", so reverse the sense. */
clear |= flag->set;
set |= flag->clear;
break;
} else if (wmemcmp(start, flag->wname + 2, end - start)
== 0) {
/* Matched "XXXX", so don't reverse. */
set |= flag->set;
clear |= flag->clear;
break;
}
}
/* Ignore unknown flag names. */
if (flag->wname == NULL && failed == NULL)
failed = start;
/* Find start of next token. */
start = end;
while (*start == L'\t' || *start == L' ' || *start == L',')
start++;
}
if (setp)
*setp = set;
if (clrp)
*clrp = clear;
/* Return location of first failure. */
return (failed);
}
#ifdef TEST
#include <stdio.h>
int
main(int argc, char **argv)
{
struct archive_entry *entry = archive_entry_new();
unsigned long set, clear;
const wchar_t *remainder;
remainder = archive_entry_copy_fflags_text_w(entry, L"nosappnd dump archive,,,,,,,");
archive_entry_fflags(entry, &set, &clear);
wprintf(L"set=0x%lX clear=0x%lX remainder='%ls'\n", set, clear, remainder);
wprintf(L"new flags='%s'\n", archive_entry_fflags_text(entry));
return (0);
}
#endif