/* * Copyright (c) 2007 Rob Braun * 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. * 3. Neither the name of Rob Braun nor the names of his contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. */ /* * 03-Apr-2005 * DRI: Rob Braun */ /* * Portions Copyright 2003, Apple Computer, Inc. * filetype_name() and associated structure. * DRI: Kevin Van Vechten */ /* * Portions Copyright 2006, Apple Computer, Inc. * Christopher Ryan */ #define _FILE_OFFSET_BITS 64 #include "config.h" #ifndef HAVE_ASPRINTF #include "asprintf.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_ACL_H #include #endif #include "xar.h" #include "arcmod.h" #include "archive.h" #include "util.h" #ifndef LLONG_MIN #define LLONG_MIN LONG_LONG_MIN #endif #ifndef LLONG_MAX #define LLONG_MAX LONG_LONG_MAX #endif static struct { const char *name; mode_t type; } filetypes [] = { { "file", S_IFREG }, { "directory", S_IFDIR }, { "symlink", S_IFLNK }, { "fifo", S_IFIFO }, { "character special", S_IFCHR }, { "block special", S_IFBLK }, { "socket", S_IFSOCK }, #ifdef S_IFWHT { "whiteout", S_IFWHT }, #endif { NULL, 0 } }; static const char * filetype_name (mode_t mode) { unsigned int i; for (i = 0; filetypes[i].name; i++) if (mode == filetypes[i].type) return (filetypes[i].name); return ("unknown"); } static xar_file_t xar_link_lookup(xar_t x, dev_t dev, ino_t ino, xar_file_t f) { char key[32]; xar_file_t ret; memset(key, 0, sizeof(key)); snprintf(key, sizeof(key)-1, "%08" DEV_HEXSTRING "%08" INO_HEXSTRING, DEV_CAST dev, INO_CAST ino); ret = xmlHashLookup(XAR(x)->ino_hash, BAD_CAST(key)); if( ret == NULL ) { xmlHashAddEntry(XAR(x)->ino_hash, BAD_CAST(key), XAR_FILE(f)); return NULL; } return ret; } static int32_t aacls(xar_file_t f, const char *file) { #ifdef HAVE_SYS_ACL_H #if !defined(__APPLE__) acl_t a; const char *type; xar_prop_get(f, "type", &type); if( !type || (strcmp(type, "symlink") == 0) ) return 0; a = acl_get_file(file, ACL_TYPE_DEFAULT); if( a ) { char *t; acl_entry_t e; /* If the acl is empty, or not valid, skip it */ if( acl_get_entry(a, ACL_FIRST_ENTRY, &e) != 1 ) goto NEXT; t = acl_to_text(a, NULL); if( t ) { xar_prop_set(f, "acl/default", t); acl_free(t); } acl_free(a); } NEXT: a = acl_get_file(file, ACL_TYPE_ACCESS); if( a ) { char *t; acl_entry_t e; /* If the acl is empty, or not valid, skip it */ if( acl_get_entry(a, ACL_FIRST_ENTRY, &e) != 1 ) goto DONE; t = acl_to_text(a, NULL); if( t ) { xar_prop_set(f, "acl/access", t); acl_free(t); } acl_free(a); } DONE: #else /* !__AAPLE__ */ acl_entry_t e = NULL; acl_t a; int i; a = acl_get_file(file, ACL_TYPE_EXTENDED); if( !a ) return 0; for( i = 0; acl_get_entry(a, e == NULL ? ACL_FIRST_ENTRY : ACL_NEXT_ENTRY, &e) == 0; i++ ) { char *t; t = acl_to_text(a, NULL); if( t ) { xar_prop_set(f, "acl/appleextended", t); acl_free(t); } } acl_free(a); #endif /* !__APPLE__ */ #endif return 0; } static int32_t eacls(xar_t x, xar_file_t f, const char *file) { #ifdef HAVE_SYS_ACL_H #if !defined(__APPLE__) const char *t; acl_t a; const char *type; xar_prop_get(f, "type", &type); if( !type || (strcmp(type, "symlink") == 0) ) return 0; xar_prop_get(f, "acl/default", &t); if( t ) { a = acl_from_text(t); if( !a ) { xar_err_new(x); xar_err_set_errno(x, errno); xar_err_set_string(x, "Error extracting default acl from toc"); xar_err_set_file(x, f); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } else { if( acl_set_file(file, ACL_TYPE_DEFAULT, a) != 0 ) { xar_err_new(x); xar_err_set_errno(x, errno); xar_err_set_string(x, "Error setting default acl"); xar_err_set_file(x, f); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } } } xar_prop_get(f, "acl/access", &t); if( t ) { a = acl_from_text(t); if( !a ) { xar_err_new(x); xar_err_set_errno(x, errno); xar_err_set_string(x, "Error extracting access acl from toc"); xar_err_set_file(x, f); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } else { if( acl_set_file(file, ACL_TYPE_ACCESS, a) != 0 ) { xar_err_new(x); xar_err_set_errno(x, errno); xar_err_set_string(x, "Error setting access acl"); xar_err_set_file(x, f); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } acl_free(a); } } #else /* !__APPLE__ */ const char *t; acl_t a; xar_prop_get(f, "acl/appleextended", &t); if( t ) { a = acl_from_text(t); if( !a ) { xar_err_new(x); xar_err_set_errno(x, errno); xar_err_set_string(x, "Error extracting access acl from toc"); xar_err_set_file(x, f); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } else { if( acl_set_file(file, ACL_TYPE_ACCESS, a) != 0 ) { xar_err_new(x); xar_err_set_errno(x, errno); xar_err_set_string(x, "Error setting access acl"); xar_err_set_file(x, f); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } acl_free(a); } } #endif /* !__APPLE__ */ #endif return 0; } #ifdef HAVE_STRUCT_STAT_ST_FLAGS #define XAR_FLAG_FORK "flags" static void x_addflag(xar_file_t f, const char *name) { char opt[1024]; memset(opt, 0, sizeof(opt)); snprintf(opt, sizeof(opt)-1, "%s/%s", XAR_FLAG_FORK, name); xar_prop_set(f, opt, NULL); return; } #endif static int32_t flags_archive(xar_file_t f, const struct stat *sb) { #ifdef HAVE_STRUCT_STAT_ST_FLAGS if( !sb->st_flags ) return 0; #ifdef UF_NODUMP if( sb->st_flags & UF_NODUMP ) x_addflag(f, "UserNoDump"); #endif #ifdef UF_IMMUTABLE if( sb->st_flags & UF_IMMUTABLE ) x_addflag(f, "UserImmutable"); #endif #ifdef UF_APPEND if( sb->st_flags & UF_APPEND ) x_addflag(f, "UserAppend"); #endif #ifdef UF_OPAQUE if( sb->st_flags & UF_OPAQUE ) x_addflag(f, "UserOpaque"); #endif #ifdef SF_ARCHIVED if( sb->st_flags & SF_ARCHIVED ) x_addflag(f, "SystemArchived"); #endif #ifdef SF_IMMUTABLE if( sb->st_flags & SF_IMMUTABLE ) x_addflag(f, "SystemImmutable"); #endif #ifdef SF_APPEND if( sb->st_flags & SF_APPEND ) x_addflag(f, "SystemAppend"); #endif #endif /* HAVE_STRUCT_STAT_ST_FLAGS */ return 0; } #ifdef HAVE_CHFLAGS static int32_t x_getprop(xar_file_t f, const char *name, char **value) { char v[1024]; memset(v, 0, sizeof(v)); snprintf(v, sizeof(v)-1, "%s/%s", XAR_FLAG_FORK, name); return xar_prop_get(f, v, (const char **)value); } #endif int32_t xar_flags_extract(xar_t x, xar_file_t f, const char *file, char *buffer, size_t len) { #ifdef HAVE_CHFLAGS char *tmp; u_int flags = 0; if( xar_prop_get(f, XAR_FLAG_FORK, NULL) ) return 0; #ifdef UF_NODUMP if( x_getprop(f, "UserNoDump", (char **)&tmp) == 0 ) flags |= UF_NODUMP; #endif #ifdef UF_IMMUTABLE if( x_getprop(f, "UserImmutable", (char **)&tmp) == 0 ) flags |= UF_IMMUTABLE; #endif #ifdef UF_APPEND if( x_getprop(f, "UserAppend", (char **)&tmp) == 0 ) flags |= UF_APPEND; #endif #ifdef UF_OPAQUE if( x_getprop(f, "UserOpaque", (char **)&tmp) == 0 ) flags |= UF_OPAQUE; #endif #ifdef SF_ARCHIVED if( x_getprop(f, "SystemArchived", (char **)&tmp) == 0 ) flags |= SF_ARCHIVED; #endif #ifdef SF_IMMUTABLE if( x_getprop(f, "SystemImmutable", (char **)&tmp) == 0 ) flags |= SF_IMMUTABLE; #endif #ifdef SF_APPEND if( x_getprop(f, "SystemAppend", (char **)&tmp) == 0 ) flags |= SF_APPEND; #endif if( !flags ) return 0; if( chflags(file, flags) != 0 ) { char e[1024]; memset(e, 0, sizeof(e)); snprintf(e, sizeof(e)-1, "chflags: %s", strerror(errno)); xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, e); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); return -1; } #endif return 0; } int32_t xar_stat_archive(xar_t x, xar_file_t f, const char *file, const char *buffer, size_t len) { char *tmpstr; struct passwd *pw; struct group *gr; char time[128]; struct tm t; const char *type; /* no stat attributes for data from a buffer, it is just a file */ if(len){ xar_prop_set(f, "type", "file"); return 0; } if( S_ISREG(XAR(x)->sbcache.st_mode) && (XAR(x)->sbcache.st_nlink > 1) ) { xar_file_t tmpf; const char *id = xar_attr_get(f, NULL, "id"); if( !id ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "stat: No file id for file"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_CREATION); return -1; } tmpf = xar_link_lookup(x, XAR(x)->sbcache.st_dev, XAR(x)->sbcache.st_ino, f); xar_prop_set(f, "type", "hardlink"); if( tmpf ) { const char *id; id = xar_attr_get(tmpf, NULL, "id"); xar_attr_set(f, "type", "link", id); } else { xar_attr_set(f, "type", "link", "original"); } } else { type = filetype_name(XAR(x)->sbcache.st_mode & S_IFMT); xar_prop_set(f, "type", type); } /* Record major/minor device node numbers */ if( S_ISBLK(XAR(x)->sbcache.st_mode) || S_ISCHR(XAR(x)->sbcache.st_mode)) { uint32_t major, minor; char tmpstr[12]; xar_devmake(XAR(x)->sbcache.st_rdev, &major, &minor); memset(tmpstr, 0, sizeof(tmpstr)); snprintf(tmpstr, sizeof(tmpstr)-1, "%u", major); xar_prop_set(f, "device/major", tmpstr); memset(tmpstr, 0, sizeof(tmpstr)); snprintf(tmpstr, sizeof(tmpstr)-1, "%u", minor); xar_prop_set(f, "device/minor", tmpstr); } if( S_ISLNK(XAR(x)->sbcache.st_mode) ) { char link[4096]; struct stat lsb; memset(link, 0, sizeof(link)); readlink(file, link, sizeof(link)-1); xar_prop_set(f, "link", link); if( stat(file, &lsb) != 0 ) { xar_attr_set(f, "link", "type", "broken"); } else { type = filetype_name(lsb.st_mode & S_IFMT); xar_attr_set(f, "link", "type", type); } } asprintf(&tmpstr, "%04o", XAR(x)->sbcache.st_mode & (~S_IFMT)); xar_prop_set(f, "mode", tmpstr); free(tmpstr); asprintf(&tmpstr, "%"PRIu64, (uint64_t)XAR(x)->sbcache.st_uid); xar_prop_set(f, "uid", tmpstr); free(tmpstr); pw = getpwuid(XAR(x)->sbcache.st_uid); if( pw ) xar_prop_set(f, "user", pw->pw_name); asprintf(&tmpstr, "%"PRIu64, (uint64_t)XAR(x)->sbcache.st_gid); xar_prop_set(f, "gid", tmpstr); free(tmpstr); gr = getgrgid(XAR(x)->sbcache.st_gid); if( gr ) xar_prop_set(f, "group", gr->gr_name); gmtime_r(&XAR(x)->sbcache.st_atime, &t); memset(time, 0, sizeof(time)); strftime(time, sizeof(time), "%FT%T", &t); strcat(time, "Z"); xar_prop_set(f, "atime", time); gmtime_r(&XAR(x)->sbcache.st_mtime, &t); memset(time, 0, sizeof(time)); strftime(time, sizeof(time), "%FT%T", &t); strcat(time, "Z"); xar_prop_set(f, "mtime", time); gmtime_r(&XAR(x)->sbcache.st_ctime, &t); memset(time, 0, sizeof(time)); strftime(time, sizeof(time), "%FT%T", &t); strcat(time, "Z"); xar_prop_set(f, "ctime", time); flags_archive(f, &(XAR(x)->sbcache)); aacls(f, file); return 0; } int32_t xar_set_perm(xar_t x, xar_file_t f, const char *file, char *buffer, size_t len) { const char *opt; int32_t m=0, mset=0; uid_t u; gid_t g; const char *timestr; struct tm t; enum {ATIME=0, MTIME}; struct timeval tv[2]; /* when writing to a buffer, there are no permissions to set */ if ( len ) return 0; /* in case we don't find anything useful in the archive */ u = geteuid(); g = getegid(); opt = xar_opt_get(x, XAR_OPT_OWNERSHIP); if( opt && (strcmp(opt, XAR_OPT_VAL_SYMBOLIC) == 0) ) { struct passwd *pw; struct group *gr; xar_prop_get(f, "user", &opt); if( opt ) { pw = getpwnam(opt); if( pw ) { u = pw->pw_uid; } } xar_prop_get(f, "group", &opt); if( opt ) { gr = getgrnam(opt); if( gr ) { g = gr->gr_gid; } } } if( opt && (strcmp(opt, XAR_OPT_VAL_NUMERIC) == 0) ) { xar_prop_get(f, "uid", &opt); if( opt ) { long long tmp; tmp = strtol(opt, NULL, 10); if( ( (tmp == LLONG_MIN) || (tmp == LLONG_MAX) ) && (errno == ERANGE) ) { return -1; } u = (uid_t)tmp; } xar_prop_get(f, "gid", &opt); if( opt ) { long long tmp; tmp = strtol(opt, NULL, 10); if( ( (tmp == LLONG_MIN) || (tmp == LLONG_MAX) ) && (errno == ERANGE) ) { return -1; } g = (gid_t)tmp; } } xar_prop_get(f, "mode", &opt); if( opt ) { long long tmp; tmp = strtoll(opt, NULL, 8); if( ( (tmp == LLONG_MIN) || (tmp == LLONG_MAX) ) && (errno == ERANGE) ) { return -1; } m = (mode_t)tmp; mset = 1; } xar_prop_get(f, "type", &opt); if( opt && !mset ) { mode_t u = umask(0); umask(u); if( strcmp(opt, "directory") == 0 ) { m = (mode_t)(0777 & ~u); } else { m = (mode_t)(0666 & ~u); } mset = 1; } if( opt && (strcmp(opt, "symlink") == 0) ) { #ifdef HAVE_LCHOWN if( lchown(file, u, g) ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "perm: could not lchown symlink"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } #ifdef HAVE_LCHMOD if( mset ) if( lchmod(file, m) ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "perm: could not lchmod symlink"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } #endif #endif } else { if( chown(file, u, g) ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "perm: could not chown file"); xar_err_set_errno(x, errno); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } if( mset ) if( chmod(file, m) ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "perm: could not chmod file"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } } eacls(x, f, file); memset(tv, 0, sizeof(struct timeval) * 2); xar_prop_get(f, "atime", ×tr); if( timestr ) { memset(&t, 0, sizeof(t)); strptime(timestr, "%FT%T", &t); tv[ATIME].tv_sec = timegm(&t); } else { tv[ATIME].tv_sec = time(NULL); } xar_prop_get(f, "mtime", ×tr); if( timestr ) { memset(&t, 0, sizeof(t)); strptime(timestr, "%FT%T", &t); tv[MTIME].tv_sec = timegm(&t); } else { tv[MTIME].tv_sec = time(NULL); } utimes(file, tv); return 0; } int32_t xar_stat_extract(xar_t x, xar_file_t f, const char *file, char *buffer, size_t len) { const char *opt; int ret, fd; mode_t modet = 0; xar_prop_get(f, "type", &opt); if(opt && (strcmp(opt, "character special") == 0)) modet = S_IFCHR; if(opt && (strcmp(opt, "block special") == 0)) modet = S_IFBLK; if( modet ) { uint32_t major, minor; long long tmpll; dev_t devt; xar_prop_get(f, "device/major", &opt); tmpll = strtoll(opt, NULL, 10); if( ( (tmpll == LLONG_MIN) || (tmpll == LLONG_MAX) ) && (errno == ERANGE) ) return -1; if( (tmpll < 0) || (tmpll > 255) ) return -1; major = tmpll; xar_prop_get(f, "device/minor", &opt); tmpll = strtoll(opt, NULL, 10); if( ( (tmpll == LLONG_MIN) || (tmpll == LLONG_MAX) ) && (errno == ERANGE) ) return -1; if( (tmpll < 0) || (tmpll > 255) ) return -1; minor = tmpll; devt = xar_makedev(major, minor); unlink(file); if( mknod(file, modet, devt) ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "mknod: Could not create character device"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); return -1; } return 0; } if(opt && (strcmp(opt, "directory") == 0)) { ret = mkdir(file, 0700); if( (ret != 0) && (errno != EEXIST) ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "stat: Could not create directory"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); return ret; } return 0; } if(opt && (strcmp(opt, "symlink") == 0)) { xar_prop_get(f, "link", &opt); if( opt ) { unlink(file); ret = symlink(opt, file); if( ret != 0 ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "stat: Could not create symlink"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); } return ret; } } if(opt && (strcmp(opt, "hardlink") == 0)) { xar_file_t tmpf; opt = xar_attr_get(f, "type", "link"); if( !opt ) return 0; if( strcmp(opt, "original") == 0 ) goto CREATEFILE; tmpf = xmlHashLookup(XAR(x)->link_hash, BAD_CAST(opt)); if( !tmpf ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "stat: Encountered hardlink with no original"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); return -1; } unlink(file); if( link(XAR_FILE(tmpf)->fspath, file) != 0 ) { if( errno == ENOENT ) { xar_iter_t i; const char *ptr; i = xar_iter_new(x); for(ptr = xar_prop_first(tmpf, i); ptr; ptr = xar_prop_next(i)) { xar_iter_t a; const char *val = NULL; const char *akey, *aval; if( strncmp("data", ptr, 4) != 0 ) continue; if( xar_prop_get(tmpf, ptr, &val) ) continue; xar_prop_set(f, ptr, val); a = xar_iter_new(x); for(akey = xar_attr_first(tmpf, ptr, a); akey; akey = xar_attr_next(a)) { aval = xar_attr_get(tmpf, ptr, akey); xar_attr_set(f, ptr, akey, aval); } xar_iter_free(a); } xar_iter_free(i); xar_attr_set(f, "type", "link", "original"); return 0; } else { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "stat: Could not link hardlink to original"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); return -1; } } return 0; } if(opt && (strcmp(opt, "fifo") == 0)) { unlink(file); if( mkfifo(file, 0) ) { xar_err_new(x); xar_err_set_file(x, f); xar_err_set_string(x, "mkfifo: Could not create fifo"); xar_err_callback(x, XAR_SEVERITY_NONFATAL, XAR_ERR_ARCHIVE_EXTRACTION); return -1; } return 0; } /* skip sockets */ if(opt && (strcmp(opt, "socket") == 0)) { return 0; } CREATEFILE: unlink(file); fd = open(file, O_RDWR|O_CREAT|O_TRUNC, 0600); if( fd > 0 ) close(fd); return 0; }