32364a7ddb
This generalizes the existing set/unset tracking for hardlink/symlink fields and extends it to cover non-string fields. Eventually, this will be further extended to cover most fields. In particular, this is needed to correctly detect when time fields are missing (for example, reading ustar archives doesn't set atime or ctime) for proper time restore and is helpful when trying to determine whether to overwrite data when restoring hardlinks. This commit updates the tests but not the docs.
2117 lines
51 KiB
C
2117 lines
51 KiB
C
/*-
|
|
* 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.
|
|
* 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$");
|
|
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#ifdef MAJOR_IN_MKDEV
|
|
#include <sys/mkdev.h>
|
|
#else
|
|
#ifdef MAJOR_IN_SYSMACROS
|
|
#include <sys/sysmacros.h>
|
|
#endif
|
|
#endif
|
|
#ifdef HAVE_EXT2FS_EXT2_FS_H
|
|
#include <ext2fs/ext2_fs.h> /* for Linux file flags */
|
|
#endif
|
|
#ifdef HAVE_LIMITS_H
|
|
#include <limits.h>
|
|
#endif
|
|
#ifdef HAVE_LINUX_FS_H
|
|
#include <linux/fs.h> /* for Linux file flags */
|
|
#endif
|
|
#ifdef HAVE_LINUX_EXT2_FS_H
|
|
#include <linux/ext2_fs.h> /* for Linux file flags */
|
|
#endif
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_WCHAR_H
|
|
#include <wchar.h>
|
|
#endif
|
|
|
|
#include "archive.h"
|
|
#include "archive_entry.h"
|
|
#include "archive_private.h"
|
|
#include "archive_entry_private.h"
|
|
|
|
#undef max
|
|
#define max(a, b) ((a)>(b)?(a):(b))
|
|
|
|
/* Play games to come up with a suitable makedev() definition. */
|
|
#ifdef __QNXNTO__
|
|
/* QNX. <sigh> */
|
|
#include <sys/netmgr.h>
|
|
#define ae_makedev(maj, min) makedev(ND_LOCAL_NODE, (maj), (min))
|
|
#elif defined makedev
|
|
/* There's a "makedev" macro. */
|
|
#define ae_makedev(maj, min) makedev((maj), (min))
|
|
#elif defined mkdev || defined _WIN32 || defined __WIN32__
|
|
/* Windows. <sigh> */
|
|
#define ae_makedev(maj, min) mkdev((maj), (min))
|
|
#else
|
|
/* There's a "makedev" function. */
|
|
#define ae_makedev(maj, min) makedev((maj), (min))
|
|
#endif
|
|
|
|
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 int aes_set_mbs(struct aes *, const char *mbs);
|
|
static int aes_copy_mbs(struct aes *, const char *mbs);
|
|
/* static void aes_set_wcs(struct aes *, const wchar_t *wcs); */
|
|
static int aes_copy_wcs(struct aes *, const wchar_t *wcs);
|
|
static int aes_copy_wcs_len(struct aes *, const wchar_t *wcs, size_t);
|
|
|
|
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 const char *ae_strtofflags(const char *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 int isint_w(const wchar_t *start, const wchar_t *end, int *result);
|
|
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);
|
|
static void
|
|
archive_entry_acl_add_entry_w_len(struct archive_entry *entry, int type,
|
|
int permset, int tag, int id, const wchar_t *name, size_t);
|
|
|
|
|
|
#ifndef HAVE_WCSCPY
|
|
static wchar_t * wcscpy(wchar_t *s1, const wchar_t *s2)
|
|
{
|
|
wchar_t *dest = s1;
|
|
while ((*s1 = *s2) != L'\0')
|
|
++s1, ++s2;
|
|
return dest;
|
|
}
|
|
#endif
|
|
#ifndef HAVE_WCSLEN
|
|
static size_t wcslen(const wchar_t *s)
|
|
{
|
|
const wchar_t *p = s;
|
|
while (*p != L'\0')
|
|
++p;
|
|
return p - s;
|
|
}
|
|
#endif
|
|
#ifndef HAVE_WMEMCMP
|
|
/* Good enough for simple equality testing, but not for sorting. */
|
|
#define wmemcmp(a,b,i) memcmp((a), (b), (i) * sizeof(wchar_t))
|
|
#endif
|
|
#ifndef HAVE_WMEMCPY
|
|
#define wmemcpy(a,b,i) (wchar_t *)memcpy((a), (b), (i) * sizeof(wchar_t))
|
|
#endif
|
|
|
|
static void
|
|
aes_clean(struct aes *aes)
|
|
{
|
|
if (aes->aes_wcs) {
|
|
free((wchar_t *)(uintptr_t)aes->aes_wcs);
|
|
aes->aes_wcs = NULL;
|
|
}
|
|
archive_string_free(&(aes->aes_mbs));
|
|
archive_string_free(&(aes->aes_utf8));
|
|
aes->aes_set = 0;
|
|
}
|
|
|
|
static void
|
|
aes_copy(struct aes *dest, struct aes *src)
|
|
{
|
|
wchar_t *wp;
|
|
|
|
dest->aes_set = src->aes_set;
|
|
archive_string_copy(&(dest->aes_mbs), &(src->aes_mbs));
|
|
archive_string_copy(&(dest->aes_utf8), &(src->aes_utf8));
|
|
|
|
if (src->aes_wcs != NULL) {
|
|
wp = (wchar_t *)malloc((wcslen(src->aes_wcs) + 1)
|
|
* sizeof(wchar_t));
|
|
if (wp == NULL)
|
|
__archive_errx(1, "No memory for aes_copy()");
|
|
wcscpy(wp, src->aes_wcs);
|
|
dest->aes_wcs = wp;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
aes_get_utf8(struct aes *aes)
|
|
{
|
|
if (aes->aes_set & AES_SET_UTF8)
|
|
return (aes->aes_utf8.s);
|
|
if ((aes->aes_set & AES_SET_WCS)
|
|
&& archive_strappend_w_utf8(&(aes->aes_utf8), aes->aes_wcs) != NULL) {
|
|
aes->aes_set |= AES_SET_UTF8;
|
|
return (aes->aes_utf8.s);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static const char *
|
|
aes_get_mbs(struct aes *aes)
|
|
{
|
|
/* If we already have an MBS form, return that immediately. */
|
|
if (aes->aes_set & AES_SET_MBS)
|
|
return (aes->aes_mbs.s);
|
|
/* If there's a WCS form, try converting with the native locale. */
|
|
if ((aes->aes_set & AES_SET_WCS)
|
|
&& archive_strappend_w_mbs(&(aes->aes_mbs), aes->aes_wcs) != NULL) {
|
|
aes->aes_set |= AES_SET_MBS;
|
|
return (aes->aes_mbs.s);
|
|
}
|
|
/* We'll use UTF-8 for MBS if all else fails. */
|
|
return (aes_get_utf8(aes));
|
|
}
|
|
|
|
static const wchar_t *
|
|
aes_get_wcs(struct aes *aes)
|
|
{
|
|
wchar_t *w;
|
|
int r;
|
|
|
|
/* Return WCS form if we already have it. */
|
|
if (aes->aes_set & AES_SET_WCS)
|
|
return (aes->aes_wcs);
|
|
|
|
if (aes->aes_set & AES_SET_MBS) {
|
|
/* Try converting MBS to WCS using native locale. */
|
|
/*
|
|
* No single byte will be more than one wide character,
|
|
* so this length estimate will always be big enough.
|
|
*/
|
|
size_t wcs_length = aes->aes_mbs.length;
|
|
|
|
w = (wchar_t *)malloc((wcs_length + 1) * sizeof(wchar_t));
|
|
if (w == NULL)
|
|
__archive_errx(1, "No memory for aes_get_wcs()");
|
|
r = mbstowcs(w, aes->aes_mbs.s, wcs_length);
|
|
w[wcs_length] = 0;
|
|
if (r > 0) {
|
|
aes->aes_set |= AES_SET_WCS;
|
|
return (aes->aes_wcs = w);
|
|
}
|
|
free(w);
|
|
}
|
|
|
|
if (aes->aes_set & AES_SET_UTF8) {
|
|
/* Try converting UTF8 to WCS. */
|
|
aes->aes_wcs = __archive_string_utf8_w(&(aes->aes_utf8));
|
|
aes->aes_set |= AES_SET_WCS;
|
|
return (aes->aes_wcs);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static int
|
|
aes_set_mbs(struct aes *aes, const char *mbs)
|
|
{
|
|
return (aes_copy_mbs(aes, mbs));
|
|
}
|
|
|
|
static int
|
|
aes_copy_mbs(struct aes *aes, const char *mbs)
|
|
{
|
|
if (mbs == NULL) {
|
|
aes->aes_set = 0;
|
|
return (0);
|
|
}
|
|
aes->aes_set = AES_SET_MBS; /* Only MBS form is set now. */
|
|
archive_strcpy(&(aes->aes_mbs), mbs);
|
|
archive_string_empty(&(aes->aes_utf8));
|
|
if (aes->aes_wcs) {
|
|
free((wchar_t *)(uintptr_t)aes->aes_wcs);
|
|
aes->aes_wcs = NULL;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* The 'update' form tries to proactively update all forms of
|
|
* this string (WCS and MBS) and returns an error if any of
|
|
* them fail. This is used by the 'pax' handler, for instance,
|
|
* to detect and report character-conversion failures early while
|
|
* still allowing clients to get potentially useful values from
|
|
* the more tolerant lazy conversions. (get_mbs and get_wcs will
|
|
* strive to give the user something useful, so you can get hopefully
|
|
* usable values even if some of the character conversions are failing.)
|
|
*/
|
|
static int
|
|
aes_update_utf8(struct aes *aes, const char *utf8)
|
|
{
|
|
if (utf8 == NULL) {
|
|
aes->aes_set = 0;
|
|
return (1); /* Succeeded in clearing everything. */
|
|
}
|
|
|
|
/* Save the UTF8 string. */
|
|
archive_strcpy(&(aes->aes_utf8), utf8);
|
|
|
|
/* Empty the mbs and wcs strings. */
|
|
archive_string_empty(&(aes->aes_mbs));
|
|
if (aes->aes_wcs) {
|
|
free((wchar_t *)(uintptr_t)aes->aes_wcs);
|
|
aes->aes_wcs = NULL;
|
|
}
|
|
|
|
aes->aes_set = AES_SET_UTF8; /* Only UTF8 is set now. */
|
|
|
|
/* TODO: We should just do a direct UTF-8 to MBS conversion
|
|
* here. That would be faster, use less space, and give the
|
|
* same information. (If a UTF-8 to MBS conversion succeeds,
|
|
* then UTF-8->WCS and Unicode->MBS conversions will both
|
|
* succeed.) */
|
|
|
|
/* Try converting UTF8 to WCS, return false on failure. */
|
|
aes->aes_wcs = __archive_string_utf8_w(&(aes->aes_utf8));
|
|
if (aes->aes_wcs == NULL)
|
|
return (0);
|
|
aes->aes_set = AES_SET_UTF8 | AES_SET_WCS; /* Both UTF8 and WCS set. */
|
|
|
|
/* Try converting WCS to MBS, return false on failure. */
|
|
if (archive_strappend_w_mbs(&(aes->aes_mbs), aes->aes_wcs) == NULL)
|
|
return (0);
|
|
aes->aes_set = AES_SET_UTF8 | AES_SET_WCS | AES_SET_MBS;
|
|
|
|
/* All conversions succeeded. */
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
aes_copy_wcs(struct aes *aes, const wchar_t *wcs)
|
|
{
|
|
return aes_copy_wcs_len(aes, wcs, wcs == NULL ? 0 : wcslen(wcs));
|
|
}
|
|
|
|
static int
|
|
aes_copy_wcs_len(struct aes *aes, const wchar_t *wcs, size_t len)
|
|
{
|
|
wchar_t *w;
|
|
|
|
if (wcs == NULL) {
|
|
aes->aes_set = 0;
|
|
return (0);
|
|
}
|
|
aes->aes_set = AES_SET_WCS; /* Only WCS form set. */
|
|
archive_string_empty(&(aes->aes_mbs));
|
|
archive_string_empty(&(aes->aes_utf8));
|
|
if (aes->aes_wcs) {
|
|
free((wchar_t *)(uintptr_t)aes->aes_wcs);
|
|
aes->aes_wcs = NULL;
|
|
}
|
|
w = (wchar_t *)malloc((len + 1) * sizeof(wchar_t));
|
|
if (w == NULL)
|
|
__archive_errx(1, "No memory for aes_copy_wcs()");
|
|
wmemcpy(w, wcs, len);
|
|
w[len] = L'\0';
|
|
aes->aes_wcs = w;
|
|
return (0);
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* Public Interface
|
|
*
|
|
****************************************************************************/
|
|
|
|
struct archive_entry *
|
|
archive_entry_clear(struct archive_entry *entry)
|
|
{
|
|
if (entry == NULL)
|
|
return (NULL);
|
|
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);
|
|
archive_entry_xattr_clear(entry);
|
|
free(entry->stat);
|
|
memset(entry, 0, sizeof(*entry));
|
|
return entry;
|
|
}
|
|
|
|
struct archive_entry *
|
|
archive_entry_clone(struct archive_entry *entry)
|
|
{
|
|
struct archive_entry *entry2;
|
|
struct ae_acl *ap, *ap2;
|
|
struct ae_xattr *xp;
|
|
|
|
/* Allocate new structure and copy over all of the fields. */
|
|
entry2 = (struct archive_entry *)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);
|
|
entry2->ae_set = entry->ae_set;
|
|
aes_copy(&entry2->ae_uname, &entry->ae_uname);
|
|
|
|
/* Copy ACL data over. */
|
|
ap = entry->acl_head;
|
|
while (ap != NULL) {
|
|
ap2 = acl_new_entry(entry2,
|
|
ap->type, ap->permset, ap->tag, ap->id);
|
|
if (ap2 != NULL)
|
|
aes_copy(&ap2->name, &ap->name);
|
|
ap = ap->next;
|
|
}
|
|
|
|
/* Copy xattr data over. */
|
|
xp = entry->xattr_head;
|
|
while (xp != NULL) {
|
|
archive_entry_xattr_add_entry(entry2,
|
|
xp->name, xp->value, xp->size);
|
|
xp = xp->next;
|
|
}
|
|
|
|
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 = (struct archive_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.aest_atime);
|
|
}
|
|
|
|
long
|
|
archive_entry_atime_nsec(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_atime_nsec);
|
|
}
|
|
|
|
int
|
|
archive_entry_atime_is_set(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_set & AE_SET_ATIME);
|
|
}
|
|
|
|
time_t
|
|
archive_entry_ctime(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_ctime);
|
|
}
|
|
|
|
int
|
|
archive_entry_ctime_is_set(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_set & AE_SET_CTIME);
|
|
}
|
|
|
|
long
|
|
archive_entry_ctime_nsec(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_ctime_nsec);
|
|
}
|
|
|
|
dev_t
|
|
archive_entry_dev(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_stat.aest_dev_is_broken_down)
|
|
return ae_makedev(entry->ae_stat.aest_devmajor,
|
|
entry->ae_stat.aest_devminor);
|
|
else
|
|
return (entry->ae_stat.aest_dev);
|
|
}
|
|
|
|
dev_t
|
|
archive_entry_devmajor(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_stat.aest_dev_is_broken_down)
|
|
return (entry->ae_stat.aest_devmajor);
|
|
else
|
|
return major(entry->ae_stat.aest_dev);
|
|
}
|
|
|
|
dev_t
|
|
archive_entry_devminor(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_stat.aest_dev_is_broken_down)
|
|
return (entry->ae_stat.aest_devminor);
|
|
else
|
|
return minor(entry->ae_stat.aest_dev);
|
|
}
|
|
|
|
mode_t
|
|
archive_entry_filetype(struct archive_entry *entry)
|
|
{
|
|
return (AE_IFMT & entry->ae_stat.aest_mode);
|
|
}
|
|
|
|
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.aest_gid);
|
|
}
|
|
|
|
const char *
|
|
archive_entry_gname(struct archive_entry *entry)
|
|
{
|
|
return (aes_get_mbs(&entry->ae_gname));
|
|
}
|
|
|
|
const wchar_t *
|
|
archive_entry_gname_w(struct archive_entry *entry)
|
|
{
|
|
return (aes_get_wcs(&entry->ae_gname));
|
|
}
|
|
|
|
const char *
|
|
archive_entry_hardlink(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_set & AE_SET_HARDLINK)
|
|
return (aes_get_mbs(&entry->ae_hardlink));
|
|
return (NULL);
|
|
}
|
|
|
|
const wchar_t *
|
|
archive_entry_hardlink_w(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_set & AE_SET_HARDLINK)
|
|
return (aes_get_wcs(&entry->ae_hardlink));
|
|
return (NULL);
|
|
}
|
|
|
|
ino_t
|
|
archive_entry_ino(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_ino);
|
|
}
|
|
|
|
mode_t
|
|
archive_entry_mode(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_mode);
|
|
}
|
|
|
|
time_t
|
|
archive_entry_mtime(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_mtime);
|
|
}
|
|
|
|
long
|
|
archive_entry_mtime_nsec(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_mtime_nsec);
|
|
}
|
|
|
|
int
|
|
archive_entry_mtime_is_set(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_set & AE_SET_MTIME);
|
|
}
|
|
|
|
unsigned int
|
|
archive_entry_nlink(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_nlink);
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (entry->ae_stat.aest_rdev_is_broken_down)
|
|
return ae_makedev(entry->ae_stat.aest_rdevmajor,
|
|
entry->ae_stat.aest_rdevminor);
|
|
else
|
|
return (entry->ae_stat.aest_rdev);
|
|
}
|
|
|
|
dev_t
|
|
archive_entry_rdevmajor(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_stat.aest_rdev_is_broken_down)
|
|
return (entry->ae_stat.aest_rdevmajor);
|
|
else
|
|
return major(entry->ae_stat.aest_rdev);
|
|
}
|
|
|
|
dev_t
|
|
archive_entry_rdevminor(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_stat.aest_rdev_is_broken_down)
|
|
return (entry->ae_stat.aest_rdevminor);
|
|
else
|
|
return minor(entry->ae_stat.aest_rdev);
|
|
}
|
|
|
|
int64_t
|
|
archive_entry_size(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_size);
|
|
}
|
|
|
|
int
|
|
archive_entry_size_is_set(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_set & AE_SET_SIZE);
|
|
}
|
|
|
|
const char *
|
|
archive_entry_sourcepath(struct archive_entry *entry)
|
|
{
|
|
return (aes_get_mbs(&entry->ae_sourcepath));
|
|
}
|
|
|
|
const char *
|
|
archive_entry_symlink(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_set & AE_SET_SYMLINK)
|
|
return (aes_get_mbs(&entry->ae_symlink));
|
|
return (NULL);
|
|
}
|
|
|
|
const wchar_t *
|
|
archive_entry_symlink_w(struct archive_entry *entry)
|
|
{
|
|
if (entry->ae_set & AE_SET_SYMLINK)
|
|
return (aes_get_wcs(&entry->ae_symlink));
|
|
return (NULL);
|
|
}
|
|
|
|
uid_t
|
|
archive_entry_uid(struct archive_entry *entry)
|
|
{
|
|
return (entry->ae_stat.aest_uid);
|
|
}
|
|
|
|
const char *
|
|
archive_entry_uname(struct archive_entry *entry)
|
|
{
|
|
return (aes_get_mbs(&entry->ae_uname));
|
|
}
|
|
|
|
const wchar_t *
|
|
archive_entry_uname_w(struct archive_entry *entry)
|
|
{
|
|
return (aes_get_wcs(&entry->ae_uname));
|
|
}
|
|
|
|
/*
|
|
* Functions to set archive_entry properties.
|
|
*/
|
|
|
|
void
|
|
archive_entry_set_filetype(struct archive_entry *entry, unsigned int type)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_mode &= ~AE_IFMT;
|
|
entry->ae_stat.aest_mode |= AE_IFMT & type;
|
|
}
|
|
|
|
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 char *
|
|
archive_entry_copy_fflags_text(struct archive_entry *entry,
|
|
const char *flags)
|
|
{
|
|
aes_copy_mbs(&entry->ae_fflags_text, flags);
|
|
return (ae_strtofflags(flags,
|
|
&entry->ae_fflags_set, &entry->ae_fflags_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->stat_valid = 0;
|
|
entry->ae_stat.aest_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(struct archive_entry *entry, const char *name)
|
|
{
|
|
aes_copy_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);
|
|
}
|
|
|
|
int
|
|
archive_entry_update_gname_utf8(struct archive_entry *entry, const char *name)
|
|
{
|
|
return (aes_update_utf8(&entry->ae_gname, name));
|
|
}
|
|
|
|
void
|
|
archive_entry_set_ino(struct archive_entry *entry, unsigned long ino)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_ino = ino;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_hardlink(struct archive_entry *entry, const char *target)
|
|
{
|
|
aes_set_mbs(&entry->ae_hardlink, target);
|
|
if (target != NULL)
|
|
entry->ae_set |= AE_SET_HARDLINK;
|
|
else
|
|
entry->ae_set &= ~AE_SET_HARDLINK;
|
|
}
|
|
|
|
void
|
|
archive_entry_copy_hardlink(struct archive_entry *entry, const char *target)
|
|
{
|
|
aes_copy_mbs(&entry->ae_hardlink, target);
|
|
if (target != NULL)
|
|
entry->ae_set |= AE_SET_HARDLINK;
|
|
else
|
|
entry->ae_set &= ~AE_SET_HARDLINK;
|
|
}
|
|
|
|
void
|
|
archive_entry_copy_hardlink_w(struct archive_entry *entry, const wchar_t *target)
|
|
{
|
|
aes_copy_wcs(&entry->ae_hardlink, target);
|
|
if (target != NULL)
|
|
entry->ae_set |= AE_SET_HARDLINK;
|
|
else
|
|
entry->ae_set &= ~AE_SET_HARDLINK;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_atime(struct archive_entry *entry, time_t t, long ns)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_set |= AE_SET_ATIME;
|
|
entry->ae_stat.aest_atime = t;
|
|
entry->ae_stat.aest_atime_nsec = ns;
|
|
}
|
|
|
|
void
|
|
archive_entry_unset_atime(struct archive_entry *entry)
|
|
{
|
|
archive_entry_set_atime(entry, 0, 0);
|
|
entry->ae_set &= ~AE_SET_ATIME;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_ctime(struct archive_entry *entry, time_t t, long ns)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_set |= AE_SET_CTIME;
|
|
entry->ae_stat.aest_ctime = t;
|
|
entry->ae_stat.aest_ctime_nsec = ns;
|
|
}
|
|
|
|
void
|
|
archive_entry_unset_ctime(struct archive_entry *entry)
|
|
{
|
|
archive_entry_set_ctime(entry, 0, 0);
|
|
entry->ae_set &= ~AE_SET_CTIME;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_dev(struct archive_entry *entry, dev_t d)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_dev_is_broken_down = 0;
|
|
entry->ae_stat.aest_dev = d;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_devmajor(struct archive_entry *entry, dev_t m)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_dev_is_broken_down = 1;
|
|
entry->ae_stat.aest_devmajor = m;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_devminor(struct archive_entry *entry, dev_t m)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_dev_is_broken_down = 1;
|
|
entry->ae_stat.aest_devminor = m;
|
|
}
|
|
|
|
/* 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_set & AE_SET_SYMLINK)
|
|
aes_set_mbs(&entry->ae_symlink, target);
|
|
else
|
|
aes_set_mbs(&entry->ae_hardlink, target);
|
|
}
|
|
|
|
/* Set symlink if symlink is already set, else set hardlink. */
|
|
void
|
|
archive_entry_copy_link(struct archive_entry *entry, const char *target)
|
|
{
|
|
if (entry->ae_set & AE_SET_SYMLINK)
|
|
aes_copy_mbs(&entry->ae_symlink, target);
|
|
else
|
|
aes_copy_mbs(&entry->ae_hardlink, target);
|
|
}
|
|
|
|
/* Set symlink if symlink is already set, else set hardlink. */
|
|
void
|
|
archive_entry_copy_link_w(struct archive_entry *entry, const wchar_t *target)
|
|
{
|
|
if (entry->ae_set & AE_SET_SYMLINK)
|
|
aes_copy_wcs(&entry->ae_symlink, target);
|
|
else
|
|
aes_copy_wcs(&entry->ae_hardlink, target);
|
|
}
|
|
|
|
int
|
|
archive_entry_update_link_utf8(struct archive_entry *entry, const char *target)
|
|
{
|
|
if (entry->ae_set & AE_SET_SYMLINK)
|
|
return (aes_update_utf8(&entry->ae_symlink, target));
|
|
else
|
|
return (aes_update_utf8(&entry->ae_hardlink, target));
|
|
}
|
|
|
|
void
|
|
archive_entry_set_mode(struct archive_entry *entry, mode_t m)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_mode = m;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_mtime(struct archive_entry *entry, time_t m, long ns)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_set |= AE_SET_MTIME;
|
|
entry->ae_stat.aest_mtime = m;
|
|
entry->ae_stat.aest_mtime_nsec = ns;
|
|
}
|
|
|
|
void
|
|
archive_entry_unset_mtime(struct archive_entry *entry)
|
|
{
|
|
archive_entry_set_mtime(entry, 0, 0);
|
|
entry->ae_set &= ~AE_SET_MTIME;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_nlink(struct archive_entry *entry, unsigned int nlink)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_nlink = nlink;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int
|
|
archive_entry_update_pathname_utf8(struct archive_entry *entry, const char *name)
|
|
{
|
|
return (aes_update_utf8(&entry->ae_pathname, name));
|
|
}
|
|
|
|
void
|
|
archive_entry_set_perm(struct archive_entry *entry, mode_t p)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_mode &= AE_IFMT;
|
|
entry->ae_stat.aest_mode |= ~AE_IFMT & p;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_rdev(struct archive_entry *entry, dev_t m)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_rdev = m;
|
|
entry->ae_stat.aest_rdev_is_broken_down = 0;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_rdevmajor(struct archive_entry *entry, dev_t m)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_rdev_is_broken_down = 1;
|
|
entry->ae_stat.aest_rdevmajor = m;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_rdevminor(struct archive_entry *entry, dev_t m)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_rdev_is_broken_down = 1;
|
|
entry->ae_stat.aest_rdevminor = m;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_size(struct archive_entry *entry, int64_t s)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_size = s;
|
|
entry->ae_set |= AE_SET_SIZE;
|
|
}
|
|
|
|
void
|
|
archive_entry_unset_size(struct archive_entry *entry)
|
|
{
|
|
archive_entry_set_size(entry, 0);
|
|
entry->ae_set &= ~AE_SET_SIZE;
|
|
}
|
|
|
|
void
|
|
archive_entry_copy_sourcepath(struct archive_entry *entry, const char *path)
|
|
{
|
|
aes_set_mbs(&entry->ae_sourcepath, path);
|
|
}
|
|
|
|
void
|
|
archive_entry_set_symlink(struct archive_entry *entry, const char *linkname)
|
|
{
|
|
aes_set_mbs(&entry->ae_symlink, linkname);
|
|
if (linkname != NULL)
|
|
entry->ae_set |= AE_SET_SYMLINK;
|
|
else
|
|
entry->ae_set &= ~AE_SET_SYMLINK;
|
|
}
|
|
|
|
void
|
|
archive_entry_copy_symlink(struct archive_entry *entry, const char *linkname)
|
|
{
|
|
aes_copy_mbs(&entry->ae_symlink, linkname);
|
|
if (linkname != NULL)
|
|
entry->ae_set |= AE_SET_SYMLINK;
|
|
else
|
|
entry->ae_set &= ~AE_SET_SYMLINK;
|
|
}
|
|
|
|
void
|
|
archive_entry_copy_symlink_w(struct archive_entry *entry, const wchar_t *linkname)
|
|
{
|
|
aes_copy_wcs(&entry->ae_symlink, linkname);
|
|
if (linkname != NULL)
|
|
entry->ae_set |= AE_SET_SYMLINK;
|
|
else
|
|
entry->ae_set &= ~AE_SET_SYMLINK;
|
|
}
|
|
|
|
void
|
|
archive_entry_set_uid(struct archive_entry *entry, uid_t u)
|
|
{
|
|
entry->stat_valid = 0;
|
|
entry->ae_stat.aest_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(struct archive_entry *entry, const char *name)
|
|
{
|
|
aes_copy_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);
|
|
}
|
|
|
|
int
|
|
archive_entry_update_uname_utf8(struct archive_entry *entry, const char *name)
|
|
{
|
|
return (aes_update_utf8(&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)
|
|
{
|
|
archive_entry_acl_add_entry_w_len(entry, type, permset, tag, id, name, wcslen(name));
|
|
}
|
|
|
|
void
|
|
archive_entry_acl_add_entry_w_len(struct archive_entry *entry,
|
|
int type, int permset, int tag, int id, const wchar_t *name, size_t len)
|
|
{
|
|
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' && len > 0)
|
|
aes_copy_wcs_len(&ap->name, name, len);
|
|
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.aest_mode &= ~0700;
|
|
entry->ae_stat.aest_mode |= (permset & 7) << 6;
|
|
return (0);
|
|
case ARCHIVE_ENTRY_ACL_GROUP_OBJ:
|
|
entry->ae_stat.aest_mode &= ~0070;
|
|
entry->ae_stat.aest_mode |= (permset & 7) << 3;
|
|
return (0);
|
|
case ARCHIVE_ENTRY_ACL_OTHER:
|
|
entry->ae_stat.aest_mode &= ~0007;
|
|
entry->ae_stat.aest_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 = (struct ae_acl *)malloc(sizeof(*ap));
|
|
if (ap == NULL)
|
|
return (NULL);
|
|
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.aest_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.aest_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.aest_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.aest_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;
|
|
*type = 0;
|
|
*permset = 0;
|
|
*tag = 0;
|
|
*id = -1;
|
|
*name = NULL;
|
|
return (ARCHIVE_EOF); /* End of ACL entries. */
|
|
}
|
|
*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;
|
|
size_t 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);
|
|
else
|
|
length += sizeof(uid_t) * 3 + 1;
|
|
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 = (wchar_t *)malloc(length * sizeof(wchar_t));
|
|
if (wp == NULL)
|
|
__archive_errx(1, "No memory to generate the text version of the ACL");
|
|
count = 0;
|
|
if ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
|
|
append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_USER_OBJ, NULL,
|
|
entry->ae_stat.aest_mode & 0700, -1);
|
|
*wp++ = ',';
|
|
append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_GROUP_OBJ, NULL,
|
|
entry->ae_stat.aest_mode & 0070, -1);
|
|
*wp++ = ',';
|
|
append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_OTHER, NULL,
|
|
entry->ae_stat.aest_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 < 0)
|
|
id = 0;
|
|
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;
|
|
/* FALLTHROUGH */
|
|
case ARCHIVE_ENTRY_ACL_USER:
|
|
wcscpy(*wp, L"user");
|
|
break;
|
|
case ARCHIVE_ENTRY_ACL_GROUP_OBJ:
|
|
wname = NULL;
|
|
id = -1;
|
|
/* FALLTHROUGH */
|
|
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);
|
|
} else if (tag == ARCHIVE_ENTRY_ACL_USER
|
|
|| tag == ARCHIVE_ENTRY_ACL_GROUP) {
|
|
append_id_w(wp, id);
|
|
id = -1;
|
|
}
|
|
*(*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)
|
|
{
|
|
struct {
|
|
const wchar_t *start;
|
|
const wchar_t *end;
|
|
} field[4];
|
|
|
|
int fields;
|
|
int type, tag, permset, id;
|
|
const wchar_t *p;
|
|
wchar_t sep;
|
|
|
|
while (text != NULL && *text != L'\0') {
|
|
/*
|
|
* Parse the fields out of the next entry,
|
|
* advance 'text' to start of next entry.
|
|
*/
|
|
fields = 0;
|
|
do {
|
|
const wchar_t *start, *end;
|
|
next_field_w(&text, &start, &end, &sep);
|
|
if (fields < 4) {
|
|
field[fields].start = start;
|
|
field[fields].end = end;
|
|
}
|
|
++fields;
|
|
} while (sep == L':');
|
|
|
|
if (fields < 3)
|
|
return (ARCHIVE_WARN);
|
|
|
|
/* Check for a numeric ID in field 1 or 3. */
|
|
id = -1;
|
|
isint_w(field[1].start, field[1].end, &id);
|
|
/* Field 3 is optional. */
|
|
if (id == -1 && fields > 3)
|
|
isint_w(field[3].start, field[3].end, &id);
|
|
|
|
/* Parse the permissions from field 2. */
|
|
permset = 0;
|
|
p = field[2].start;
|
|
while (p < field[2].end) {
|
|
switch (*p++) {
|
|
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:
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Solaris extension: "defaultuser::rwx" is the
|
|
* default ACL corresponding to "user::rwx", etc.
|
|
*/
|
|
if (field[0].end-field[0].start > 7
|
|
&& wmemcmp(field[0].start, L"default", 7) == 0) {
|
|
type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT;
|
|
field[0].start += 7;
|
|
} else
|
|
type = default_type;
|
|
|
|
if (prefix_w(field[0].start, field[0].end, L"user")) {
|
|
if (id != -1 || field[1].start < field[1].end)
|
|
tag = ARCHIVE_ENTRY_ACL_USER;
|
|
else
|
|
tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
|
|
} else if (prefix_w(field[0].start, field[0].end, L"group")) {
|
|
if (id != -1 || field[1].start < field[1].end)
|
|
tag = ARCHIVE_ENTRY_ACL_GROUP;
|
|
else
|
|
tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
|
|
} else if (prefix_w(field[0].start, field[0].end, L"other")) {
|
|
if (id != -1 || field[1].start < field[1].end)
|
|
return (ARCHIVE_WARN);
|
|
tag = ARCHIVE_ENTRY_ACL_OTHER;
|
|
} else if (prefix_w(field[0].start, field[0].end, L"mask")) {
|
|
if (id != -1 || field[1].start < field[1].end)
|
|
return (ARCHIVE_WARN);
|
|
tag = ARCHIVE_ENTRY_ACL_MASK;
|
|
} else
|
|
return (ARCHIVE_WARN);
|
|
|
|
/* Add entry to the internal list. */
|
|
archive_entry_acl_add_entry_w_len(entry, type, permset,
|
|
tag, id, field[1].start, field[1].end - field[1].start);
|
|
}
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
/*
|
|
* extended attribute handling
|
|
*/
|
|
|
|
void
|
|
archive_entry_xattr_clear(struct archive_entry *entry)
|
|
{
|
|
struct ae_xattr *xp;
|
|
|
|
while (entry->xattr_head != NULL) {
|
|
xp = entry->xattr_head->next;
|
|
free(entry->xattr_head->name);
|
|
free(entry->xattr_head->value);
|
|
free(entry->xattr_head);
|
|
entry->xattr_head = xp;
|
|
}
|
|
|
|
entry->xattr_head = NULL;
|
|
}
|
|
|
|
void
|
|
archive_entry_xattr_add_entry(struct archive_entry *entry,
|
|
const char *name, const void *value, size_t size)
|
|
{
|
|
struct ae_xattr *xp;
|
|
|
|
for (xp = entry->xattr_head; xp != NULL; xp = xp->next)
|
|
;
|
|
|
|
if ((xp = (struct ae_xattr *)malloc(sizeof(struct ae_xattr))) == NULL)
|
|
/* XXX Error XXX */
|
|
return;
|
|
|
|
xp->name = strdup(name);
|
|
if ((xp->value = malloc(size)) != NULL) {
|
|
memcpy(xp->value, value, size);
|
|
xp->size = size;
|
|
} else
|
|
xp->size = 0;
|
|
|
|
xp->next = entry->xattr_head;
|
|
entry->xattr_head = xp;
|
|
}
|
|
|
|
|
|
/*
|
|
* returns number of the extended attribute entries
|
|
*/
|
|
int
|
|
archive_entry_xattr_count(struct archive_entry *entry)
|
|
{
|
|
struct ae_xattr *xp;
|
|
int count = 0;
|
|
|
|
for (xp = entry->xattr_head; xp != NULL; xp = xp->next)
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
int
|
|
archive_entry_xattr_reset(struct archive_entry * entry)
|
|
{
|
|
entry->xattr_p = entry->xattr_head;
|
|
|
|
return archive_entry_xattr_count(entry);
|
|
}
|
|
|
|
int
|
|
archive_entry_xattr_next(struct archive_entry * entry,
|
|
const char **name, const void **value, size_t *size)
|
|
{
|
|
if (entry->xattr_p) {
|
|
*name = entry->xattr_p->name;
|
|
*value = entry->xattr_p->value;
|
|
*size = entry->xattr_p->size;
|
|
|
|
entry->xattr_p = entry->xattr_p->next;
|
|
|
|
return (ARCHIVE_OK);
|
|
} else {
|
|
*name = NULL;
|
|
*value = NULL;
|
|
*size = (size_t)0;
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* end of xattr handling
|
|
*/
|
|
|
|
/*
|
|
* Parse a string to a positive decimal integer. Returns true if
|
|
* the string is non-empty and consists only of decimal digits,
|
|
* false otherwise.
|
|
*/
|
|
static int
|
|
isint_w(const wchar_t *start, const wchar_t *end, int *result)
|
|
{
|
|
int n = 0;
|
|
if (start >= end)
|
|
return (0);
|
|
while (start < end) {
|
|
if (*start < '0' || *start > '9')
|
|
return (0);
|
|
if (n > (INT_MAX / 10))
|
|
n = INT_MAX;
|
|
else {
|
|
n *= 10;
|
|
n += *start - '0';
|
|
}
|
|
start++;
|
|
}
|
|
*result = n;
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* 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)++;
|
|
}
|
|
|
|
/*
|
|
* Return true if the characters [start...end) are a prefix of 'test'.
|
|
* This makes it easy to handle the obvious abbreviations: 'u' for 'user', etc.
|
|
*/
|
|
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.
|
|
*/
|
|
static char *
|
|
ae_fflagstostr(unsigned long bitset, unsigned long bitclear)
|
|
{
|
|
char *string, *dp;
|
|
const char *sp;
|
|
unsigned long bits;
|
|
struct flag *flag;
|
|
size_t 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 = (char *)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);
|
|
}
|
|
|
|
/*
|
|
* strtofflags --
|
|
* 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.
|
|
*/
|
|
static const char *
|
|
ae_strtofflags(const char *s, unsigned long *setp, unsigned long *clrp)
|
|
{
|
|
const char *start, *end;
|
|
struct flag *flag;
|
|
unsigned long set, clear;
|
|
const char *failed;
|
|
|
|
set = clear = 0;
|
|
start = s;
|
|
failed = NULL;
|
|
/* Find start of first token. */
|
|
while (*start == '\t' || *start == ' ' || *start == ',')
|
|
start++;
|
|
while (*start != '\0') {
|
|
/* Locate end of token. */
|
|
end = start;
|
|
while (*end != '\0' && *end != '\t' &&
|
|
*end != ' ' && *end != ',')
|
|
end++;
|
|
for (flag = flags; flag->name != NULL; flag++) {
|
|
if (memcmp(start, flag->name, end - start) == 0) {
|
|
/* Matched "noXXXX", so reverse the sense. */
|
|
clear |= flag->set;
|
|
set |= flag->clear;
|
|
break;
|
|
} else if (memcmp(start, flag->name + 2, end - start)
|
|
== 0) {
|
|
/* Matched "XXXX", so don't reverse. */
|
|
set |= flag->set;
|
|
clear |= flag->clear;
|
|
break;
|
|
}
|
|
}
|
|
/* Ignore unknown flag names. */
|
|
if (flag->name == NULL && failed == NULL)
|
|
failed = start;
|
|
|
|
/* Find start of next token. */
|
|
start = end;
|
|
while (*start == '\t' || *start == ' ' || *start == ',')
|
|
start++;
|
|
|
|
}
|
|
|
|
if (setp)
|
|
*setp = set;
|
|
if (clrp)
|
|
*clrp = clear;
|
|
|
|
/* Return location of first failure. */
|
|
return (failed);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static 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
|