1caf54c4fSMartin Matuska /*-
2caf54c4fSMartin Matuska * Copyright (c) 2003-2009 Tim Kientzle
3fd082e96SMartin Matuska * Copyright (c) 2010-2012 Michihiro NAKAJIMA
4e9ed7ea4SMartin Matuska * Copyright (c) 2016 Martin Matuska
5caf54c4fSMartin Matuska * All rights reserved.
6caf54c4fSMartin Matuska *
7caf54c4fSMartin Matuska * Redistribution and use in source and binary forms, with or without
8caf54c4fSMartin Matuska * modification, are permitted provided that the following conditions
9caf54c4fSMartin Matuska * are met:
10caf54c4fSMartin Matuska * 1. Redistributions of source code must retain the above copyright
11caf54c4fSMartin Matuska * notice, this list of conditions and the following disclaimer.
12caf54c4fSMartin Matuska * 2. Redistributions in binary form must reproduce the above copyright
13caf54c4fSMartin Matuska * notice, this list of conditions and the following disclaimer in the
14caf54c4fSMartin Matuska * documentation and/or other materials provided with the distribution.
15caf54c4fSMartin Matuska *
16caf54c4fSMartin Matuska * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
17caf54c4fSMartin Matuska * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18caf54c4fSMartin Matuska * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19caf54c4fSMartin Matuska * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
20caf54c4fSMartin Matuska * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21caf54c4fSMartin Matuska * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22caf54c4fSMartin Matuska * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23caf54c4fSMartin Matuska * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24caf54c4fSMartin Matuska * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25caf54c4fSMartin Matuska * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26caf54c4fSMartin Matuska */
27caf54c4fSMartin Matuska
28caf54c4fSMartin Matuska #include "archive_platform.h"
29caf54c4fSMartin Matuska
306c95142eSMartin Matuska /* This is the tree-walking code for POSIX systems. */
316c95142eSMartin Matuska #if !defined(_WIN32) || defined(__CYGWIN__)
326c95142eSMartin Matuska
33caf54c4fSMartin Matuska #ifdef HAVE_SYS_TYPES_H
34caf54c4fSMartin Matuska #include <sys/types.h>
35caf54c4fSMartin Matuska #endif
36caf54c4fSMartin Matuska #ifdef HAVE_SYS_EXTATTR_H
37caf54c4fSMartin Matuska #include <sys/extattr.h>
38caf54c4fSMartin Matuska #endif
396c95142eSMartin Matuska #ifdef HAVE_SYS_IOCTL_H
406c95142eSMartin Matuska #include <sys/ioctl.h>
416c95142eSMartin Matuska #endif
42caf54c4fSMartin Matuska #ifdef HAVE_SYS_PARAM_H
43caf54c4fSMartin Matuska #include <sys/param.h>
44caf54c4fSMartin Matuska #endif
45caf54c4fSMartin Matuska #ifdef HAVE_SYS_STAT_H
46caf54c4fSMartin Matuska #include <sys/stat.h>
47caf54c4fSMartin Matuska #endif
48acc60b03SMartin Matuska #if defined(HAVE_SYS_XATTR_H)
49caf54c4fSMartin Matuska #include <sys/xattr.h>
50acc60b03SMartin Matuska #elif defined(HAVE_ATTR_XATTR_H)
51acc60b03SMartin Matuska #include <attr/xattr.h>
52caf54c4fSMartin Matuska #endif
536c95142eSMartin Matuska #ifdef HAVE_SYS_EA_H
546c95142eSMartin Matuska #include <sys/ea.h>
556c95142eSMartin Matuska #endif
566c95142eSMartin Matuska #ifdef HAVE_COPYFILE_H
576c95142eSMartin Matuska #include <copyfile.h>
586c95142eSMartin Matuska #endif
59caf54c4fSMartin Matuska #ifdef HAVE_ERRNO_H
60caf54c4fSMartin Matuska #include <errno.h>
61caf54c4fSMartin Matuska #endif
626c95142eSMartin Matuska #ifdef HAVE_FCNTL_H
636c95142eSMartin Matuska #include <fcntl.h>
646c95142eSMartin Matuska #endif
65caf54c4fSMartin Matuska #ifdef HAVE_LIMITS_H
66caf54c4fSMartin Matuska #include <limits.h>
67caf54c4fSMartin Matuska #endif
68fd082e96SMartin Matuska #ifdef HAVE_LINUX_TYPES_H
69fd082e96SMartin Matuska #include <linux/types.h>
70fd082e96SMartin Matuska #endif
716c95142eSMartin Matuska #ifdef HAVE_LINUX_FIEMAP_H
726c95142eSMartin Matuska #include <linux/fiemap.h>
736c95142eSMartin Matuska #endif
746c95142eSMartin Matuska #ifdef HAVE_LINUX_FS_H
756c95142eSMartin Matuska #include <linux/fs.h>
766c95142eSMartin Matuska #endif
776c95142eSMartin Matuska /*
786c95142eSMartin Matuska * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h.
796c95142eSMartin Matuska * As the include guards don't agree, the order of include is important.
806c95142eSMartin Matuska */
816c95142eSMartin Matuska #ifdef HAVE_LINUX_EXT2_FS_H
826c95142eSMartin Matuska #include <linux/ext2_fs.h> /* for Linux file flags */
836c95142eSMartin Matuska #endif
846c95142eSMartin Matuska #if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__)
856c95142eSMartin Matuska #include <ext2fs/ext2_fs.h> /* Linux file flags, broken on Cygwin */
866c95142eSMartin Matuska #endif
876c95142eSMartin Matuska #ifdef HAVE_PATHS_H
886c95142eSMartin Matuska #include <paths.h>
896c95142eSMartin Matuska #endif
906c95142eSMartin Matuska #ifdef HAVE_UNISTD_H
916c95142eSMartin Matuska #include <unistd.h>
92caf54c4fSMartin Matuska #endif
93caf54c4fSMartin Matuska
94caf54c4fSMartin Matuska #include "archive.h"
95caf54c4fSMartin Matuska #include "archive_entry.h"
96caf54c4fSMartin Matuska #include "archive_private.h"
97caf54c4fSMartin Matuska #include "archive_read_disk_private.h"
98caf54c4fSMartin Matuska
99acc60b03SMartin Matuska #ifndef O_CLOEXEC
100acc60b03SMartin Matuska #define O_CLOEXEC 0
101acc60b03SMartin Matuska #endif
102acc60b03SMartin Matuska
1036c95142eSMartin Matuska static int setup_mac_metadata(struct archive_read_disk *,
104fd082e96SMartin Matuska struct archive_entry *, int *fd);
105c3afd20fSMartin Matuska #ifdef ARCHIVE_XATTR_FREEBSD
106c3afd20fSMartin Matuska static int setup_xattrs_namespace(struct archive_read_disk *,
107c3afd20fSMartin Matuska struct archive_entry *, int *, int);
108c3afd20fSMartin Matuska #endif
109caf54c4fSMartin Matuska static int setup_xattrs(struct archive_read_disk *,
110fd082e96SMartin Matuska struct archive_entry *, int *fd);
1116c95142eSMartin Matuska static int setup_sparse(struct archive_read_disk *,
112fd082e96SMartin Matuska struct archive_entry *, int *fd);
113d5d08d29SMartin Matuska #if defined(HAVE_LINUX_FIEMAP_H)
114d5d08d29SMartin Matuska static int setup_sparse_fiemap(struct archive_read_disk *,
115d5d08d29SMartin Matuska struct archive_entry *, int *fd);
116d5d08d29SMartin Matuska #endif
117caf54c4fSMartin Matuska
1184657548dSMartin Matuska #if !ARCHIVE_ACL_SUPPORT
1194657548dSMartin Matuska int
archive_read_disk_entry_setup_acls(struct archive_read_disk * a,struct archive_entry * entry,int * fd)1204657548dSMartin Matuska archive_read_disk_entry_setup_acls(struct archive_read_disk *a,
1214657548dSMartin Matuska struct archive_entry *entry, int *fd)
1224657548dSMartin Matuska {
1234657548dSMartin Matuska (void)a; /* UNUSED */
1244657548dSMartin Matuska (void)entry; /* UNUSED */
1254657548dSMartin Matuska (void)fd; /* UNUSED */
1264657548dSMartin Matuska return (ARCHIVE_OK);
1274657548dSMartin Matuska }
1284657548dSMartin Matuska #endif
1294657548dSMartin Matuska
1304657548dSMartin Matuska /*
1314657548dSMartin Matuska * Enter working directory and return working pathname of archive_entry.
1324657548dSMartin Matuska * If a pointer to an integer is provided and its value is below zero
1335c831a5bSMartin Matuska * open a file descriptor on this pathname.
1344657548dSMartin Matuska */
1354657548dSMartin Matuska const char *
archive_read_disk_entry_setup_path(struct archive_read_disk * a,struct archive_entry * entry,int * fd)1364657548dSMartin Matuska archive_read_disk_entry_setup_path(struct archive_read_disk *a,
1374657548dSMartin Matuska struct archive_entry *entry, int *fd)
1384657548dSMartin Matuska {
1394657548dSMartin Matuska const char *path;
1404657548dSMartin Matuska
1414657548dSMartin Matuska path = archive_entry_sourcepath(entry);
1424657548dSMartin Matuska
1434657548dSMartin Matuska if (path == NULL || (a->tree != NULL &&
1444657548dSMartin Matuska a->tree_enter_working_dir(a->tree) != 0))
1454657548dSMartin Matuska path = archive_entry_pathname(entry);
1464657548dSMartin Matuska if (path == NULL) {
1474657548dSMartin Matuska archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
1484657548dSMartin Matuska "Couldn't determine path");
1494657548dSMartin Matuska } else if (fd != NULL && *fd < 0 && a->tree != NULL &&
1504657548dSMartin Matuska (a->follow_symlinks || archive_entry_filetype(entry) != AE_IFLNK)) {
1514657548dSMartin Matuska *fd = a->open_on_current_dir(a->tree, path,
1524657548dSMartin Matuska O_RDONLY | O_NONBLOCK);
1534657548dSMartin Matuska }
1544657548dSMartin Matuska return (path);
1554657548dSMartin Matuska }
1564657548dSMartin Matuska
157caf54c4fSMartin Matuska int
archive_read_disk_entry_from_file(struct archive * _a,struct archive_entry * entry,int fd,const struct stat * st)158caf54c4fSMartin Matuska archive_read_disk_entry_from_file(struct archive *_a,
159caf54c4fSMartin Matuska struct archive_entry *entry,
1606c95142eSMartin Matuska int fd,
1616c95142eSMartin Matuska const struct stat *st)
162caf54c4fSMartin Matuska {
163caf54c4fSMartin Matuska struct archive_read_disk *a = (struct archive_read_disk *)_a;
164caf54c4fSMartin Matuska const char *path, *name;
165caf54c4fSMartin Matuska struct stat s;
166caf54c4fSMartin Matuska int initial_fd = fd;
167caf54c4fSMartin Matuska int r, r1;
168caf54c4fSMartin Matuska
169df422cb4SMartin Matuska archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_ANY,
170df422cb4SMartin Matuska "archive_read_disk_entry_from_file");
171df422cb4SMartin Matuska
172caf54c4fSMartin Matuska archive_clear_error(_a);
173caf54c4fSMartin Matuska path = archive_entry_sourcepath(entry);
174caf54c4fSMartin Matuska if (path == NULL)
175caf54c4fSMartin Matuska path = archive_entry_pathname(entry);
176caf54c4fSMartin Matuska
1776c95142eSMartin Matuska if (a->tree == NULL) {
178caf54c4fSMartin Matuska if (st == NULL) {
179caf54c4fSMartin Matuska #if HAVE_FSTAT
180caf54c4fSMartin Matuska if (fd >= 0) {
181caf54c4fSMartin Matuska if (fstat(fd, &s) != 0) {
182caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
183caf54c4fSMartin Matuska "Can't fstat");
184caf54c4fSMartin Matuska return (ARCHIVE_FAILED);
185caf54c4fSMartin Matuska }
186caf54c4fSMartin Matuska } else
187caf54c4fSMartin Matuska #endif
188caf54c4fSMartin Matuska #if HAVE_LSTAT
189caf54c4fSMartin Matuska if (!a->follow_symlinks) {
190caf54c4fSMartin Matuska if (lstat(path, &s) != 0) {
191caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
192caf54c4fSMartin Matuska "Can't lstat %s", path);
193caf54c4fSMartin Matuska return (ARCHIVE_FAILED);
194caf54c4fSMartin Matuska }
195caf54c4fSMartin Matuska } else
196caf54c4fSMartin Matuska #endif
19752c2bb75SMartin Matuska if (la_stat(path, &s) != 0) {
198caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
1996c95142eSMartin Matuska "Can't stat %s", path);
200caf54c4fSMartin Matuska return (ARCHIVE_FAILED);
201caf54c4fSMartin Matuska }
202caf54c4fSMartin Matuska st = &s;
203caf54c4fSMartin Matuska }
204caf54c4fSMartin Matuska archive_entry_copy_stat(entry, st);
20510ed66fdSMartin Matuska }
20610ed66fdSMartin Matuska
207caf54c4fSMartin Matuska /* Lookup uname/gname */
208caf54c4fSMartin Matuska name = archive_read_disk_uname(_a, archive_entry_uid(entry));
209caf54c4fSMartin Matuska if (name != NULL)
210caf54c4fSMartin Matuska archive_entry_copy_uname(entry, name);
211caf54c4fSMartin Matuska name = archive_read_disk_gname(_a, archive_entry_gid(entry));
212caf54c4fSMartin Matuska if (name != NULL)
213caf54c4fSMartin Matuska archive_entry_copy_gname(entry, name);
214caf54c4fSMartin Matuska
215caf54c4fSMartin Matuska #ifdef HAVE_STRUCT_STAT_ST_FLAGS
216caf54c4fSMartin Matuska /* On FreeBSD, we get flags for free with the stat. */
217caf54c4fSMartin Matuska /* TODO: Does this belong in copy_stat()? */
21864287048SMartin Matuska if ((a->flags & ARCHIVE_READDISK_NO_FFLAGS) == 0 && st->st_flags != 0)
219caf54c4fSMartin Matuska archive_entry_set_fflags(entry, st->st_flags, 0);
220caf54c4fSMartin Matuska #endif
221caf54c4fSMartin Matuska
22264287048SMartin Matuska #if (defined(FS_IOC_GETFLAGS) && defined(HAVE_WORKING_FS_IOC_GETFLAGS)) || \
22364287048SMartin Matuska (defined(EXT2_IOC_GETFLAGS) && defined(HAVE_WORKING_EXT2_IOC_GETFLAGS))
2246c95142eSMartin Matuska /* Linux requires an extra ioctl to pull the flags. Although
2256c95142eSMartin Matuska * this is an extra step, it has a nice side-effect: We get an
2266c95142eSMartin Matuska * open file descriptor which we can use in the subsequent lookups. */
22764287048SMartin Matuska if ((a->flags & ARCHIVE_READDISK_NO_FFLAGS) == 0 &&
22864287048SMartin Matuska (S_ISREG(st->st_mode) || S_ISDIR(st->st_mode))) {
229fd082e96SMartin Matuska if (fd < 0) {
230fd082e96SMartin Matuska if (a->tree != NULL)
231fd082e96SMartin Matuska fd = a->open_on_current_dir(a->tree, path,
232acc60b03SMartin Matuska O_RDONLY | O_NONBLOCK | O_CLOEXEC);
233fd082e96SMartin Matuska else
234acc60b03SMartin Matuska fd = open(path, O_RDONLY | O_NONBLOCK |
235acc60b03SMartin Matuska O_CLOEXEC);
236acc60b03SMartin Matuska __archive_ensure_cloexec_flag(fd);
237fd082e96SMartin Matuska }
2386c95142eSMartin Matuska if (fd >= 0) {
239acc60b03SMartin Matuska int stflags;
24064287048SMartin Matuska r = ioctl(fd,
24164287048SMartin Matuska #if defined(FS_IOC_GETFLAGS)
24264287048SMartin Matuska FS_IOC_GETFLAGS,
24364287048SMartin Matuska #else
24464287048SMartin Matuska EXT2_IOC_GETFLAGS,
24564287048SMartin Matuska #endif
24664287048SMartin Matuska &stflags);
2476c95142eSMartin Matuska if (r == 0 && stflags != 0)
2486c95142eSMartin Matuska archive_entry_set_fflags(entry, stflags, 0);
2496c95142eSMartin Matuska }
2506c95142eSMartin Matuska }
2516c95142eSMartin Matuska #endif
2526c95142eSMartin Matuska
2536c95142eSMartin Matuska #if defined(HAVE_READLINK) || defined(HAVE_READLINKAT)
254caf54c4fSMartin Matuska if (S_ISLNK(st->st_mode)) {
25579085fd3SMartin Matuska size_t linkbuffer_len = st->st_size;
2566c95142eSMartin Matuska char *linkbuffer;
2576c95142eSMartin Matuska int lnklen;
2586c95142eSMartin Matuska
25979085fd3SMartin Matuska linkbuffer = malloc(linkbuffer_len + 1);
2606c95142eSMartin Matuska if (linkbuffer == NULL) {
2616c95142eSMartin Matuska archive_set_error(&a->archive, ENOMEM,
2626c95142eSMartin Matuska "Couldn't read link data");
2636c95142eSMartin Matuska return (ARCHIVE_FAILED);
2646c95142eSMartin Matuska }
265fd082e96SMartin Matuska if (a->tree != NULL) {
2666c95142eSMartin Matuska #ifdef HAVE_READLINKAT
267fd082e96SMartin Matuska lnklen = readlinkat(a->tree_current_dir_fd(a->tree),
268fd082e96SMartin Matuska path, linkbuffer, linkbuffer_len);
269fd082e96SMartin Matuska #else
270fd082e96SMartin Matuska if (a->tree_enter_working_dir(a->tree) != 0) {
271fd082e96SMartin Matuska archive_set_error(&a->archive, errno,
272fd082e96SMartin Matuska "Couldn't read link data");
273fd082e96SMartin Matuska free(linkbuffer);
274fd082e96SMartin Matuska return (ARCHIVE_FAILED);
275fd082e96SMartin Matuska }
276fd082e96SMartin Matuska lnklen = readlink(path, linkbuffer, linkbuffer_len);
2776c95142eSMartin Matuska #endif /* HAVE_READLINKAT */
278fd082e96SMartin Matuska } else
2796c95142eSMartin Matuska lnklen = readlink(path, linkbuffer, linkbuffer_len);
280caf54c4fSMartin Matuska if (lnklen < 0) {
281caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
282caf54c4fSMartin Matuska "Couldn't read link data");
2836c95142eSMartin Matuska free(linkbuffer);
284caf54c4fSMartin Matuska return (ARCHIVE_FAILED);
285caf54c4fSMartin Matuska }
28679085fd3SMartin Matuska linkbuffer[lnklen] = '\0';
287caf54c4fSMartin Matuska archive_entry_set_symlink(entry, linkbuffer);
2886c95142eSMartin Matuska free(linkbuffer);
289caf54c4fSMartin Matuska }
2906c95142eSMartin Matuska #endif /* HAVE_READLINK || HAVE_READLINKAT */
291caf54c4fSMartin Matuska
29264287048SMartin Matuska r = 0;
29364287048SMartin Matuska if ((a->flags & ARCHIVE_READDISK_NO_ACL) == 0)
2944657548dSMartin Matuska r = archive_read_disk_entry_setup_acls(a, entry, &fd);
29564287048SMartin Matuska if ((a->flags & ARCHIVE_READDISK_NO_XATTR) == 0) {
296fd082e96SMartin Matuska r1 = setup_xattrs(a, entry, &fd);
297caf54c4fSMartin Matuska if (r1 < r)
298caf54c4fSMartin Matuska r = r1;
299cdf63a70SMartin Matuska }
30064287048SMartin Matuska if (a->flags & ARCHIVE_READDISK_MAC_COPYFILE) {
301fd082e96SMartin Matuska r1 = setup_mac_metadata(a, entry, &fd);
3026c95142eSMartin Matuska if (r1 < r)
3036c95142eSMartin Matuska r = r1;
304fd082e96SMartin Matuska }
305833a452eSMartin Matuska if ((a->flags & ARCHIVE_READDISK_NO_SPARSE) == 0) {
306fd082e96SMartin Matuska r1 = setup_sparse(a, entry, &fd);
3076c95142eSMartin Matuska if (r1 < r)
3086c95142eSMartin Matuska r = r1;
309833a452eSMartin Matuska }
3106c95142eSMartin Matuska
311caf54c4fSMartin Matuska /* If we opened the file earlier in this function, close it. */
312caf54c4fSMartin Matuska if (initial_fd != fd)
313caf54c4fSMartin Matuska close(fd);
314caf54c4fSMartin Matuska return (r);
315caf54c4fSMartin Matuska }
316caf54c4fSMartin Matuska
3176c95142eSMartin Matuska #if defined(__APPLE__) && defined(HAVE_COPYFILE_H)
3186c95142eSMartin Matuska /*
3196c95142eSMartin Matuska * The Mac OS "copyfile()" API copies the extended metadata for a
3206c95142eSMartin Matuska * file into a separate file in AppleDouble format (see RFC 1740).
3216c95142eSMartin Matuska *
3226c95142eSMartin Matuska * Mac OS tar and cpio implementations store this extended
3236c95142eSMartin Matuska * metadata as a separate entry just before the regular entry
3246c95142eSMartin Matuska * with a "._" prefix added to the filename.
3256c95142eSMartin Matuska *
3266c95142eSMartin Matuska * Note that this is currently done unconditionally; the tar program has
3276c95142eSMartin Matuska * an option to discard this information before the archive is written.
3286c95142eSMartin Matuska *
3296c95142eSMartin Matuska * TODO: If there's a failure, report it and return ARCHIVE_WARN.
3306c95142eSMartin Matuska */
3316c95142eSMartin Matuska static int
setup_mac_metadata(struct archive_read_disk * a,struct archive_entry * entry,int * fd)3326c95142eSMartin Matuska setup_mac_metadata(struct archive_read_disk *a,
333fd082e96SMartin Matuska struct archive_entry *entry, int *fd)
3346c95142eSMartin Matuska {
3356c95142eSMartin Matuska int tempfd = -1;
3366c95142eSMartin Matuska int copyfile_flags = COPYFILE_NOFOLLOW | COPYFILE_ACL | COPYFILE_XATTR;
3376c95142eSMartin Matuska struct stat copyfile_stat;
3386c95142eSMartin Matuska int ret = ARCHIVE_OK;
339acc60b03SMartin Matuska void *buff = NULL;
3406c95142eSMartin Matuska int have_attrs;
341acc60b03SMartin Matuska const char *name, *tempdir;
342acc60b03SMartin Matuska struct archive_string tempfile;
3436c95142eSMartin Matuska
344fd082e96SMartin Matuska (void)fd; /* UNUSED */
3454657548dSMartin Matuska
3464657548dSMartin Matuska name = archive_read_disk_entry_setup_path(a, entry, NULL);
3476c95142eSMartin Matuska if (name == NULL)
3486c95142eSMartin Matuska return (ARCHIVE_WARN);
3496c95142eSMartin Matuska
3506c95142eSMartin Matuska /* Short-circuit if there's nothing to do. */
3516c95142eSMartin Matuska have_attrs = copyfile(name, NULL, 0, copyfile_flags | COPYFILE_CHECK);
3526c95142eSMartin Matuska if (have_attrs == -1) {
3536c95142eSMartin Matuska archive_set_error(&a->archive, errno,
3546c95142eSMartin Matuska "Could not check extended attributes");
3556c95142eSMartin Matuska return (ARCHIVE_WARN);
3566c95142eSMartin Matuska }
3576c95142eSMartin Matuska if (have_attrs == 0)
3586c95142eSMartin Matuska return (ARCHIVE_OK);
3596c95142eSMartin Matuska
3606c95142eSMartin Matuska tempdir = NULL;
3616c95142eSMartin Matuska if (issetugid() == 0)
3626c95142eSMartin Matuska tempdir = getenv("TMPDIR");
3636c95142eSMartin Matuska if (tempdir == NULL)
3646c95142eSMartin Matuska tempdir = _PATH_TMP;
365acc60b03SMartin Matuska archive_string_init(&tempfile);
366acc60b03SMartin Matuska archive_strcpy(&tempfile, tempdir);
367acc60b03SMartin Matuska archive_strcat(&tempfile, "tar.md.XXXXXX");
368acc60b03SMartin Matuska tempfd = mkstemp(tempfile.s);
369acc60b03SMartin Matuska if (tempfd < 0) {
370acc60b03SMartin Matuska archive_set_error(&a->archive, errno,
371acc60b03SMartin Matuska "Could not open extended attribute file");
372acc60b03SMartin Matuska ret = ARCHIVE_WARN;
373acc60b03SMartin Matuska goto cleanup;
374acc60b03SMartin Matuska }
375acc60b03SMartin Matuska __archive_ensure_cloexec_flag(tempfd);
3766c95142eSMartin Matuska
3776c95142eSMartin Matuska /* XXX I wish copyfile() could pack directly to a memory
3786c95142eSMartin Matuska * buffer; that would avoid the temp file here. For that
3796c95142eSMartin Matuska * matter, it would be nice if fcopyfile() actually worked,
3806c95142eSMartin Matuska * that would reduce the many open/close races here. */
381acc60b03SMartin Matuska if (copyfile(name, tempfile.s, 0, copyfile_flags | COPYFILE_PACK)) {
3826c95142eSMartin Matuska archive_set_error(&a->archive, errno,
3836c95142eSMartin Matuska "Could not pack extended attributes");
3846c95142eSMartin Matuska ret = ARCHIVE_WARN;
3856c95142eSMartin Matuska goto cleanup;
3866c95142eSMartin Matuska }
3876c95142eSMartin Matuska if (fstat(tempfd, ©file_stat)) {
3886c95142eSMartin Matuska archive_set_error(&a->archive, errno,
3896c95142eSMartin Matuska "Could not check size of extended attributes");
3906c95142eSMartin Matuska ret = ARCHIVE_WARN;
3916c95142eSMartin Matuska goto cleanup;
3926c95142eSMartin Matuska }
3936c95142eSMartin Matuska buff = malloc(copyfile_stat.st_size);
3946c95142eSMartin Matuska if (buff == NULL) {
3956c95142eSMartin Matuska archive_set_error(&a->archive, errno,
3966c95142eSMartin Matuska "Could not allocate memory for extended attributes");
3976c95142eSMartin Matuska ret = ARCHIVE_WARN;
3986c95142eSMartin Matuska goto cleanup;
3996c95142eSMartin Matuska }
4006c95142eSMartin Matuska if (copyfile_stat.st_size != read(tempfd, buff, copyfile_stat.st_size)) {
4016c95142eSMartin Matuska archive_set_error(&a->archive, errno,
4026c95142eSMartin Matuska "Could not read extended attributes into memory");
4036c95142eSMartin Matuska ret = ARCHIVE_WARN;
4046c95142eSMartin Matuska goto cleanup;
4056c95142eSMartin Matuska }
4066c95142eSMartin Matuska archive_entry_copy_mac_metadata(entry, buff, copyfile_stat.st_size);
4076c95142eSMartin Matuska
4086c95142eSMartin Matuska cleanup:
409acc60b03SMartin Matuska if (tempfd >= 0) {
4106c95142eSMartin Matuska close(tempfd);
411acc60b03SMartin Matuska unlink(tempfile.s);
412acc60b03SMartin Matuska }
413acc60b03SMartin Matuska archive_string_free(&tempfile);
414acc60b03SMartin Matuska free(buff);
4156c95142eSMartin Matuska return (ret);
4166c95142eSMartin Matuska }
4176c95142eSMartin Matuska
4186c95142eSMartin Matuska #else
4196c95142eSMartin Matuska
4206c95142eSMartin Matuska /*
4216c95142eSMartin Matuska * Stub implementation for non-Mac systems.
4226c95142eSMartin Matuska */
4236c95142eSMartin Matuska static int
setup_mac_metadata(struct archive_read_disk * a,struct archive_entry * entry,int * fd)4246c95142eSMartin Matuska setup_mac_metadata(struct archive_read_disk *a,
425fd082e96SMartin Matuska struct archive_entry *entry, int *fd)
4266c95142eSMartin Matuska {
4276c95142eSMartin Matuska (void)a; /* UNUSED */
4286c95142eSMartin Matuska (void)entry; /* UNUSED */
4296c95142eSMartin Matuska (void)fd; /* UNUSED */
4306c95142eSMartin Matuska return (ARCHIVE_OK);
4316c95142eSMartin Matuska }
4326c95142eSMartin Matuska #endif
4336c95142eSMartin Matuska
434e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX || ARCHIVE_XATTR_DARWIN || ARCHIVE_XATTR_AIX
435caf54c4fSMartin Matuska
436caf54c4fSMartin Matuska /*
437e46d4714SMartin Matuska * Linux, Darwin and AIX extended attribute support.
438caf54c4fSMartin Matuska *
439caf54c4fSMartin Matuska * TODO: By using a stack-allocated buffer for the first
440caf54c4fSMartin Matuska * call to getxattr(), we might be able to avoid the second
441caf54c4fSMartin Matuska * call entirely. We only need the second call if the
442caf54c4fSMartin Matuska * stack-allocated buffer is too small. But a modest buffer
443caf54c4fSMartin Matuska * of 1024 bytes or so will often be big enough. Same applies
444caf54c4fSMartin Matuska * to listxattr().
445caf54c4fSMartin Matuska */
446caf54c4fSMartin Matuska
447caf54c4fSMartin Matuska
448caf54c4fSMartin Matuska static int
setup_xattr(struct archive_read_disk * a,struct archive_entry * entry,const char * name,int fd,const char * accpath)449caf54c4fSMartin Matuska setup_xattr(struct archive_read_disk *a,
45064287048SMartin Matuska struct archive_entry *entry, const char *name, int fd, const char *accpath)
451caf54c4fSMartin Matuska {
452caf54c4fSMartin Matuska ssize_t size;
453caf54c4fSMartin Matuska void *value = NULL;
454caf54c4fSMartin Matuska
455e46d4714SMartin Matuska
456e46d4714SMartin Matuska if (fd >= 0) {
457e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
4586c95142eSMartin Matuska size = fgetxattr(fd, name, NULL, 0);
459e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
460e46d4714SMartin Matuska size = fgetxattr(fd, name, NULL, 0, 0, 0);
461e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
4626c95142eSMartin Matuska size = fgetea(fd, name, NULL, 0);
463e46d4714SMartin Matuska #endif
464e46d4714SMartin Matuska } else if (!a->follow_symlinks) {
465e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
466e46d4714SMartin Matuska size = lgetxattr(accpath, name, NULL, 0);
467e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
468e46d4714SMartin Matuska size = getxattr(accpath, name, NULL, 0, 0, XATTR_NOFOLLOW);
469e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
4706c95142eSMartin Matuska size = lgetea(accpath, name, NULL, 0);
471e46d4714SMartin Matuska #endif
472e46d4714SMartin Matuska } else {
473e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
474e46d4714SMartin Matuska size = getxattr(accpath, name, NULL, 0);
475e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
476e46d4714SMartin Matuska size = getxattr(accpath, name, NULL, 0, 0, 0);
477e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
4786c95142eSMartin Matuska size = getea(accpath, name, NULL, 0);
4796c95142eSMartin Matuska #endif
480e46d4714SMartin Matuska }
481caf54c4fSMartin Matuska
482caf54c4fSMartin Matuska if (size == -1) {
483caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
484caf54c4fSMartin Matuska "Couldn't query extended attribute");
485caf54c4fSMartin Matuska return (ARCHIVE_WARN);
486caf54c4fSMartin Matuska }
487caf54c4fSMartin Matuska
488caf54c4fSMartin Matuska if (size > 0 && (value = malloc(size)) == NULL) {
489caf54c4fSMartin Matuska archive_set_error(&a->archive, errno, "Out of memory");
490caf54c4fSMartin Matuska return (ARCHIVE_FATAL);
491caf54c4fSMartin Matuska }
492caf54c4fSMartin Matuska
493e46d4714SMartin Matuska
494e46d4714SMartin Matuska if (fd >= 0) {
495e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
4966c95142eSMartin Matuska size = fgetxattr(fd, name, value, size);
497e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
498e46d4714SMartin Matuska size = fgetxattr(fd, name, value, size, 0, 0);
499e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
5006c95142eSMartin Matuska size = fgetea(fd, name, value, size);
501e46d4714SMartin Matuska #endif
502e46d4714SMartin Matuska } else if (!a->follow_symlinks) {
503e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
504e46d4714SMartin Matuska size = lgetxattr(accpath, name, value, size);
505e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
506e46d4714SMartin Matuska size = getxattr(accpath, name, value, size, 0, XATTR_NOFOLLOW);
507e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
5086c95142eSMartin Matuska size = lgetea(accpath, name, value, size);
509e46d4714SMartin Matuska #endif
510e46d4714SMartin Matuska } else {
511e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
512e46d4714SMartin Matuska size = getxattr(accpath, name, value, size);
513e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
514e46d4714SMartin Matuska size = getxattr(accpath, name, value, size, 0, 0);
515e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
5166c95142eSMartin Matuska size = getea(accpath, name, value, size);
5176c95142eSMartin Matuska #endif
518e46d4714SMartin Matuska }
519caf54c4fSMartin Matuska
520caf54c4fSMartin Matuska if (size == -1) {
521caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
522caf54c4fSMartin Matuska "Couldn't read extended attribute");
523caf54c4fSMartin Matuska return (ARCHIVE_WARN);
524caf54c4fSMartin Matuska }
525caf54c4fSMartin Matuska
526caf54c4fSMartin Matuska archive_entry_xattr_add_entry(entry, name, value, size);
527caf54c4fSMartin Matuska
528caf54c4fSMartin Matuska free(value);
529caf54c4fSMartin Matuska return (ARCHIVE_OK);
530caf54c4fSMartin Matuska }
531caf54c4fSMartin Matuska
532caf54c4fSMartin Matuska static int
setup_xattrs(struct archive_read_disk * a,struct archive_entry * entry,int * fd)533caf54c4fSMartin Matuska setup_xattrs(struct archive_read_disk *a,
534fd082e96SMartin Matuska struct archive_entry *entry, int *fd)
535caf54c4fSMartin Matuska {
536caf54c4fSMartin Matuska char *list, *p;
537caf54c4fSMartin Matuska const char *path;
538caf54c4fSMartin Matuska ssize_t list_size;
539caf54c4fSMartin Matuska
54064287048SMartin Matuska path = NULL;
541caf54c4fSMartin Matuska
542fd082e96SMartin Matuska if (*fd < 0) {
5434657548dSMartin Matuska path = archive_read_disk_entry_setup_path(a, entry, fd);
5444657548dSMartin Matuska if (path == NULL)
54564287048SMartin Matuska return (ARCHIVE_WARN);
546fd082e96SMartin Matuska }
547fd082e96SMartin Matuska
548e46d4714SMartin Matuska if (*fd >= 0) {
549e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
550fd082e96SMartin Matuska list_size = flistxattr(*fd, NULL, 0);
551e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
552e46d4714SMartin Matuska list_size = flistxattr(*fd, NULL, 0, 0);
553e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
554fd082e96SMartin Matuska list_size = flistea(*fd, NULL, 0);
555e46d4714SMartin Matuska #endif
556e46d4714SMartin Matuska } else if (!a->follow_symlinks) {
557e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
558e46d4714SMartin Matuska list_size = llistxattr(path, NULL, 0);
559e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
560e46d4714SMartin Matuska list_size = listxattr(path, NULL, 0, XATTR_NOFOLLOW);
561e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
5626c95142eSMartin Matuska list_size = llistea(path, NULL, 0);
563e46d4714SMartin Matuska #endif
564e46d4714SMartin Matuska } else {
565e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
566e46d4714SMartin Matuska list_size = listxattr(path, NULL, 0);
567e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
568e46d4714SMartin Matuska list_size = listxattr(path, NULL, 0, 0);
569e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
5706c95142eSMartin Matuska list_size = listea(path, NULL, 0);
5716c95142eSMartin Matuska #endif
572e46d4714SMartin Matuska }
573caf54c4fSMartin Matuska
574caf54c4fSMartin Matuska if (list_size == -1) {
5756c95142eSMartin Matuska if (errno == ENOTSUP || errno == ENOSYS)
576caf54c4fSMartin Matuska return (ARCHIVE_OK);
577caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
578caf54c4fSMartin Matuska "Couldn't list extended attributes");
579caf54c4fSMartin Matuska return (ARCHIVE_WARN);
580caf54c4fSMartin Matuska }
581caf54c4fSMartin Matuska
582caf54c4fSMartin Matuska if (list_size == 0)
583caf54c4fSMartin Matuska return (ARCHIVE_OK);
584caf54c4fSMartin Matuska
585caf54c4fSMartin Matuska if ((list = malloc(list_size)) == NULL) {
586caf54c4fSMartin Matuska archive_set_error(&a->archive, errno, "Out of memory");
587caf54c4fSMartin Matuska return (ARCHIVE_FATAL);
588caf54c4fSMartin Matuska }
589caf54c4fSMartin Matuska
590e46d4714SMartin Matuska if (*fd >= 0) {
591e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
592fd082e96SMartin Matuska list_size = flistxattr(*fd, list, list_size);
593e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
594e46d4714SMartin Matuska list_size = flistxattr(*fd, list, list_size, 0);
595e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
596fd082e96SMartin Matuska list_size = flistea(*fd, list, list_size);
597e46d4714SMartin Matuska #endif
598e46d4714SMartin Matuska } else if (!a->follow_symlinks) {
599e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
600e46d4714SMartin Matuska list_size = llistxattr(path, list, list_size);
601e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
602e46d4714SMartin Matuska list_size = listxattr(path, list, list_size, XATTR_NOFOLLOW);
603e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
6046c95142eSMartin Matuska list_size = llistea(path, list, list_size);
605e46d4714SMartin Matuska #endif
606e46d4714SMartin Matuska } else {
607e46d4714SMartin Matuska #if ARCHIVE_XATTR_LINUX
608e46d4714SMartin Matuska list_size = listxattr(path, list, list_size);
609e46d4714SMartin Matuska #elif ARCHIVE_XATTR_DARWIN
610e46d4714SMartin Matuska list_size = listxattr(path, list, list_size, 0);
611e46d4714SMartin Matuska #elif ARCHIVE_XATTR_AIX
6126c95142eSMartin Matuska list_size = listea(path, list, list_size);
6136c95142eSMartin Matuska #endif
614e46d4714SMartin Matuska }
615caf54c4fSMartin Matuska
616caf54c4fSMartin Matuska if (list_size == -1) {
617caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
618caf54c4fSMartin Matuska "Couldn't retrieve extended attributes");
619caf54c4fSMartin Matuska free(list);
620caf54c4fSMartin Matuska return (ARCHIVE_WARN);
621caf54c4fSMartin Matuska }
622caf54c4fSMartin Matuska
623caf54c4fSMartin Matuska for (p = list; (p - list) < list_size; p += strlen(p) + 1) {
62443f9e382SMartin Matuska #if ARCHIVE_XATTR_LINUX
62543f9e382SMartin Matuska /* Linux: skip POSIX.1e ACL extended attributes */
62643f9e382SMartin Matuska if (strncmp(p, "system.", 7) == 0 &&
62743f9e382SMartin Matuska (strcmp(p + 7, "posix_acl_access") == 0 ||
62843f9e382SMartin Matuska strcmp(p + 7, "posix_acl_default") == 0))
629caf54c4fSMartin Matuska continue;
63043f9e382SMartin Matuska if (strncmp(p, "trusted.SGI_", 12) == 0 &&
63143f9e382SMartin Matuska (strcmp(p + 12, "ACL_DEFAULT") == 0 ||
63243f9e382SMartin Matuska strcmp(p + 12, "ACL_FILE") == 0))
63343f9e382SMartin Matuska continue;
63443f9e382SMartin Matuska
63543f9e382SMartin Matuska /* Linux: xfsroot namespace is obsolete and unsupported */
63643f9e382SMartin Matuska if (strncmp(p, "xfsroot.", 8) == 0)
63743f9e382SMartin Matuska continue;
63843f9e382SMartin Matuska #endif
63964287048SMartin Matuska setup_xattr(a, entry, p, *fd, path);
640caf54c4fSMartin Matuska }
641caf54c4fSMartin Matuska
642caf54c4fSMartin Matuska free(list);
643caf54c4fSMartin Matuska return (ARCHIVE_OK);
644caf54c4fSMartin Matuska }
645caf54c4fSMartin Matuska
646e46d4714SMartin Matuska #elif ARCHIVE_XATTR_FREEBSD
647caf54c4fSMartin Matuska
648caf54c4fSMartin Matuska /*
649caf54c4fSMartin Matuska * FreeBSD extattr interface.
650caf54c4fSMartin Matuska */
651caf54c4fSMartin Matuska
652caf54c4fSMartin Matuska /* TODO: Implement this. Follow the Linux model above, but
653caf54c4fSMartin Matuska * with FreeBSD-specific system calls, of course. Be careful
654caf54c4fSMartin Matuska * to not include the system extattrs that hold ACLs; we handle
655caf54c4fSMartin Matuska * those separately.
656caf54c4fSMartin Matuska */
657caf54c4fSMartin Matuska static int
658caf54c4fSMartin Matuska setup_xattr(struct archive_read_disk *a, struct archive_entry *entry,
65964287048SMartin Matuska int namespace, const char *name, const char *fullname, int fd,
66064287048SMartin Matuska const char *path);
661caf54c4fSMartin Matuska
662caf54c4fSMartin Matuska static int
setup_xattr(struct archive_read_disk * a,struct archive_entry * entry,int namespace,const char * name,const char * fullname,int fd,const char * accpath)663caf54c4fSMartin Matuska setup_xattr(struct archive_read_disk *a, struct archive_entry *entry,
66464287048SMartin Matuska int namespace, const char *name, const char *fullname, int fd,
66564287048SMartin Matuska const char *accpath)
666caf54c4fSMartin Matuska {
667caf54c4fSMartin Matuska ssize_t size;
668caf54c4fSMartin Matuska void *value = NULL;
669caf54c4fSMartin Matuska
6706c95142eSMartin Matuska if (fd >= 0)
6716c95142eSMartin Matuska size = extattr_get_fd(fd, namespace, name, NULL, 0);
6726c95142eSMartin Matuska else if (!a->follow_symlinks)
673caf54c4fSMartin Matuska size = extattr_get_link(accpath, namespace, name, NULL, 0);
674caf54c4fSMartin Matuska else
675caf54c4fSMartin Matuska size = extattr_get_file(accpath, namespace, name, NULL, 0);
676caf54c4fSMartin Matuska
677caf54c4fSMartin Matuska if (size == -1) {
678caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
679caf54c4fSMartin Matuska "Couldn't query extended attribute");
680caf54c4fSMartin Matuska return (ARCHIVE_WARN);
681caf54c4fSMartin Matuska }
682caf54c4fSMartin Matuska
683caf54c4fSMartin Matuska if (size > 0 && (value = malloc(size)) == NULL) {
684caf54c4fSMartin Matuska archive_set_error(&a->archive, errno, "Out of memory");
685caf54c4fSMartin Matuska return (ARCHIVE_FATAL);
686caf54c4fSMartin Matuska }
687caf54c4fSMartin Matuska
6886c95142eSMartin Matuska if (fd >= 0)
6896c95142eSMartin Matuska size = extattr_get_fd(fd, namespace, name, value, size);
6906c95142eSMartin Matuska else if (!a->follow_symlinks)
691caf54c4fSMartin Matuska size = extattr_get_link(accpath, namespace, name, value, size);
692caf54c4fSMartin Matuska else
693caf54c4fSMartin Matuska size = extattr_get_file(accpath, namespace, name, value, size);
694caf54c4fSMartin Matuska
695caf54c4fSMartin Matuska if (size == -1) {
696fd082e96SMartin Matuska free(value);
697caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
698caf54c4fSMartin Matuska "Couldn't read extended attribute");
699caf54c4fSMartin Matuska return (ARCHIVE_WARN);
700caf54c4fSMartin Matuska }
701caf54c4fSMartin Matuska
702caf54c4fSMartin Matuska archive_entry_xattr_add_entry(entry, fullname, value, size);
703caf54c4fSMartin Matuska
704caf54c4fSMartin Matuska free(value);
705caf54c4fSMartin Matuska return (ARCHIVE_OK);
706caf54c4fSMartin Matuska }
707caf54c4fSMartin Matuska
708caf54c4fSMartin Matuska static int
setup_xattrs_namespace(struct archive_read_disk * a,struct archive_entry * entry,int * fd,int namespace)709c3afd20fSMartin Matuska setup_xattrs_namespace(struct archive_read_disk *a,
710c3afd20fSMartin Matuska struct archive_entry *entry, int *fd, int namespace)
711caf54c4fSMartin Matuska {
712caf54c4fSMartin Matuska char buff[512];
713caf54c4fSMartin Matuska char *list, *p;
714caf54c4fSMartin Matuska ssize_t list_size;
715caf54c4fSMartin Matuska const char *path;
716caf54c4fSMartin Matuska
71764287048SMartin Matuska path = NULL;
718caf54c4fSMartin Matuska
719fd082e96SMartin Matuska if (*fd < 0) {
7204657548dSMartin Matuska path = archive_read_disk_entry_setup_path(a, entry, fd);
7214657548dSMartin Matuska if (path == NULL)
72264287048SMartin Matuska return (ARCHIVE_WARN);
723fd082e96SMartin Matuska }
724fd082e96SMartin Matuska
725fd082e96SMartin Matuska if (*fd >= 0)
726fd082e96SMartin Matuska list_size = extattr_list_fd(*fd, namespace, NULL, 0);
7276c95142eSMartin Matuska else if (!a->follow_symlinks)
728caf54c4fSMartin Matuska list_size = extattr_list_link(path, namespace, NULL, 0);
729caf54c4fSMartin Matuska else
730caf54c4fSMartin Matuska list_size = extattr_list_file(path, namespace, NULL, 0);
731caf54c4fSMartin Matuska
732caf54c4fSMartin Matuska if (list_size == -1 && errno == EOPNOTSUPP)
733caf54c4fSMartin Matuska return (ARCHIVE_OK);
734c3afd20fSMartin Matuska if (list_size == -1 && errno == EPERM)
735c3afd20fSMartin Matuska return (ARCHIVE_OK);
736caf54c4fSMartin Matuska if (list_size == -1) {
737caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
738caf54c4fSMartin Matuska "Couldn't list extended attributes");
739caf54c4fSMartin Matuska return (ARCHIVE_WARN);
740caf54c4fSMartin Matuska }
741caf54c4fSMartin Matuska
742caf54c4fSMartin Matuska if (list_size == 0)
743caf54c4fSMartin Matuska return (ARCHIVE_OK);
744caf54c4fSMartin Matuska
745caf54c4fSMartin Matuska if ((list = malloc(list_size)) == NULL) {
746caf54c4fSMartin Matuska archive_set_error(&a->archive, errno, "Out of memory");
747caf54c4fSMartin Matuska return (ARCHIVE_FATAL);
748caf54c4fSMartin Matuska }
749caf54c4fSMartin Matuska
750fd082e96SMartin Matuska if (*fd >= 0)
751fd082e96SMartin Matuska list_size = extattr_list_fd(*fd, namespace, list, list_size);
7526c95142eSMartin Matuska else if (!a->follow_symlinks)
753caf54c4fSMartin Matuska list_size = extattr_list_link(path, namespace, list, list_size);
754caf54c4fSMartin Matuska else
755caf54c4fSMartin Matuska list_size = extattr_list_file(path, namespace, list, list_size);
756caf54c4fSMartin Matuska
757caf54c4fSMartin Matuska if (list_size == -1) {
758caf54c4fSMartin Matuska archive_set_error(&a->archive, errno,
759caf54c4fSMartin Matuska "Couldn't retrieve extended attributes");
760caf54c4fSMartin Matuska free(list);
761caf54c4fSMartin Matuska return (ARCHIVE_WARN);
762caf54c4fSMartin Matuska }
763caf54c4fSMartin Matuska
764caf54c4fSMartin Matuska p = list;
765caf54c4fSMartin Matuska while ((p - list) < list_size) {
766caf54c4fSMartin Matuska size_t len = 255 & (int)*p;
767caf54c4fSMartin Matuska char *name;
768caf54c4fSMartin Matuska
769c3afd20fSMartin Matuska if (namespace == EXTATTR_NAMESPACE_SYSTEM) {
770c3afd20fSMartin Matuska if (!strcmp(p + 1, "nfs4.acl") ||
771c3afd20fSMartin Matuska !strcmp(p + 1, "posix1e.acl_access") ||
772c3afd20fSMartin Matuska !strcmp(p + 1, "posix1e.acl_default")) {
773c3afd20fSMartin Matuska p += 1 + len;
774c3afd20fSMartin Matuska continue;
775c3afd20fSMartin Matuska }
776c3afd20fSMartin Matuska strcpy(buff, "system.");
777c3afd20fSMartin Matuska } else {
778caf54c4fSMartin Matuska strcpy(buff, "user.");
779c3afd20fSMartin Matuska }
780caf54c4fSMartin Matuska name = buff + strlen(buff);
781caf54c4fSMartin Matuska memcpy(name, p + 1, len);
782caf54c4fSMartin Matuska name[len] = '\0';
78364287048SMartin Matuska setup_xattr(a, entry, namespace, name, buff, *fd, path);
784caf54c4fSMartin Matuska p += 1 + len;
785caf54c4fSMartin Matuska }
786caf54c4fSMartin Matuska
787caf54c4fSMartin Matuska free(list);
788caf54c4fSMartin Matuska return (ARCHIVE_OK);
789caf54c4fSMartin Matuska }
790caf54c4fSMartin Matuska
791c3afd20fSMartin Matuska static int
setup_xattrs(struct archive_read_disk * a,struct archive_entry * entry,int * fd)792c3afd20fSMartin Matuska setup_xattrs(struct archive_read_disk *a,
793c3afd20fSMartin Matuska struct archive_entry *entry, int *fd)
794c3afd20fSMartin Matuska {
795c3afd20fSMartin Matuska int namespaces[2];
796c3afd20fSMartin Matuska int i, res;
797c3afd20fSMartin Matuska
798c3afd20fSMartin Matuska namespaces[0] = EXTATTR_NAMESPACE_USER;
799c3afd20fSMartin Matuska namespaces[1] = EXTATTR_NAMESPACE_SYSTEM;
800c3afd20fSMartin Matuska
801c3afd20fSMartin Matuska for (i = 0; i < 2; i++) {
802c3afd20fSMartin Matuska res = setup_xattrs_namespace(a, entry, fd,
803c3afd20fSMartin Matuska namespaces[i]);
804c3afd20fSMartin Matuska switch (res) {
805c3afd20fSMartin Matuska case (ARCHIVE_OK):
806c3afd20fSMartin Matuska case (ARCHIVE_WARN):
807c3afd20fSMartin Matuska break;
808c3afd20fSMartin Matuska default:
809c3afd20fSMartin Matuska return (res);
810c3afd20fSMartin Matuska }
811c3afd20fSMartin Matuska }
812c3afd20fSMartin Matuska
813c3afd20fSMartin Matuska return (ARCHIVE_OK);
814c3afd20fSMartin Matuska }
815c3afd20fSMartin Matuska
816caf54c4fSMartin Matuska #else
817caf54c4fSMartin Matuska
818caf54c4fSMartin Matuska /*
819caf54c4fSMartin Matuska * Generic (stub) extended attribute support.
820caf54c4fSMartin Matuska */
821caf54c4fSMartin Matuska static int
setup_xattrs(struct archive_read_disk * a,struct archive_entry * entry,int * fd)822caf54c4fSMartin Matuska setup_xattrs(struct archive_read_disk *a,
823fd082e96SMartin Matuska struct archive_entry *entry, int *fd)
824caf54c4fSMartin Matuska {
825caf54c4fSMartin Matuska (void)a; /* UNUSED */
826caf54c4fSMartin Matuska (void)entry; /* UNUSED */
827caf54c4fSMartin Matuska (void)fd; /* UNUSED */
828caf54c4fSMartin Matuska return (ARCHIVE_OK);
829caf54c4fSMartin Matuska }
830caf54c4fSMartin Matuska
831caf54c4fSMartin Matuska #endif
8326c95142eSMartin Matuska
8336c95142eSMartin Matuska #if defined(HAVE_LINUX_FIEMAP_H)
8346c95142eSMartin Matuska
8356c95142eSMartin Matuska /*
836d5d08d29SMartin Matuska * Linux FIEMAP sparse interface.
8376c95142eSMartin Matuska *
8386c95142eSMartin Matuska * The FIEMAP ioctl returns an "extent" for each physical allocation
8396c95142eSMartin Matuska * on disk. We need to process those to generate a more compact list
8406c95142eSMartin Matuska * of logical file blocks. We also need to be very careful to use
8416c95142eSMartin Matuska * FIEMAP_FLAG_SYNC here, since there are reports that Linux sometimes
8426c95142eSMartin Matuska * does not report allocations for newly-written data that hasn't
8436c95142eSMartin Matuska * been synced to disk.
8446c95142eSMartin Matuska *
8456c95142eSMartin Matuska * It's important to return a minimal sparse file list because we want
8466c95142eSMartin Matuska * to not trigger sparse file extensions if we don't have to, since
8476c95142eSMartin Matuska * not all readers support them.
8486c95142eSMartin Matuska */
8496c95142eSMartin Matuska
8506c95142eSMartin Matuska static int
setup_sparse_fiemap(struct archive_read_disk * a,struct archive_entry * entry,int * fd)851d5d08d29SMartin Matuska setup_sparse_fiemap(struct archive_read_disk *a,
852fd082e96SMartin Matuska struct archive_entry *entry, int *fd)
8536c95142eSMartin Matuska {
8546c95142eSMartin Matuska char buff[4096];
8556c95142eSMartin Matuska struct fiemap *fm;
8566c95142eSMartin Matuska struct fiemap_extent *fe;
8576c95142eSMartin Matuska int64_t size;
858cdf63a70SMartin Matuska int count, do_fiemap, iters;
8596c95142eSMartin Matuska int exit_sts = ARCHIVE_OK;
8604657548dSMartin Matuska const char *path;
8616c95142eSMartin Matuska
8626c95142eSMartin Matuska if (archive_entry_filetype(entry) != AE_IFREG
8636c95142eSMartin Matuska || archive_entry_size(entry) <= 0
8646c95142eSMartin Matuska || archive_entry_hardlink(entry) != NULL)
8656c95142eSMartin Matuska return (ARCHIVE_OK);
8666c95142eSMartin Matuska
867fd082e96SMartin Matuska if (*fd < 0) {
8684657548dSMartin Matuska path = archive_read_disk_entry_setup_path(a, entry, NULL);
8696c95142eSMartin Matuska if (path == NULL)
8704657548dSMartin Matuska return (ARCHIVE_FAILED);
8714657548dSMartin Matuska
872fd082e96SMartin Matuska if (a->tree != NULL)
873fd082e96SMartin Matuska *fd = a->open_on_current_dir(a->tree, path,
874acc60b03SMartin Matuska O_RDONLY | O_NONBLOCK | O_CLOEXEC);
875fd082e96SMartin Matuska else
876acc60b03SMartin Matuska *fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
877fd082e96SMartin Matuska if (*fd < 0) {
8786c95142eSMartin Matuska archive_set_error(&a->archive, errno,
8796c95142eSMartin Matuska "Can't open `%s'", path);
8806c95142eSMartin Matuska return (ARCHIVE_FAILED);
8816c95142eSMartin Matuska }
882acc60b03SMartin Matuska __archive_ensure_cloexec_flag(*fd);
8836c95142eSMartin Matuska }
8846c95142eSMartin Matuska
885acc60b03SMartin Matuska /* Initialize buffer to avoid the error valgrind complains about. */
886acc60b03SMartin Matuska memset(buff, 0, sizeof(buff));
8876c95142eSMartin Matuska count = (sizeof(buff) - sizeof(*fm))/sizeof(*fe);
8886c95142eSMartin Matuska fm = (struct fiemap *)buff;
8896c95142eSMartin Matuska fm->fm_start = 0;
8906c95142eSMartin Matuska fm->fm_length = ~0ULL;;
8916c95142eSMartin Matuska fm->fm_flags = FIEMAP_FLAG_SYNC;
8926c95142eSMartin Matuska fm->fm_extent_count = count;
8936c95142eSMartin Matuska do_fiemap = 1;
8946c95142eSMartin Matuska size = archive_entry_size(entry);
895cdf63a70SMartin Matuska for (iters = 0; ; ++iters) {
8966c95142eSMartin Matuska int i, r;
8976c95142eSMartin Matuska
898fd082e96SMartin Matuska r = ioctl(*fd, FS_IOC_FIEMAP, fm);
8996c95142eSMartin Matuska if (r < 0) {
900fd082e96SMartin Matuska /* When something error happens, it is better we
901fd082e96SMartin Matuska * should return ARCHIVE_OK because an earlier
902a2e802b7SMartin Matuska * version(<2.6.28) cannot perform FS_IOC_FIEMAP. */
903d5d08d29SMartin Matuska goto exit_setup_sparse_fiemap;
9046c95142eSMartin Matuska }
905cdf63a70SMartin Matuska if (fm->fm_mapped_extents == 0) {
906cdf63a70SMartin Matuska if (iters == 0) {
907cdf63a70SMartin Matuska /* Fully sparse file; insert a zero-length "data" entry */
908cdf63a70SMartin Matuska archive_entry_sparse_add_entry(entry, 0, 0);
909cdf63a70SMartin Matuska }
9106c95142eSMartin Matuska break;
911cdf63a70SMartin Matuska }
9126c95142eSMartin Matuska fe = fm->fm_extents;
9136c95142eSMartin Matuska for (i = 0; i < (int)fm->fm_mapped_extents; i++, fe++) {
9146c95142eSMartin Matuska if (!(fe->fe_flags & FIEMAP_EXTENT_UNWRITTEN)) {
9156c95142eSMartin Matuska /* The fe_length of the last block does not
9166c95142eSMartin Matuska * adjust itself to its size files. */
9176c95142eSMartin Matuska int64_t length = fe->fe_length;
9186c95142eSMartin Matuska if (fe->fe_logical + length > (uint64_t)size)
9196c95142eSMartin Matuska length -= fe->fe_logical + length - size;
9206c95142eSMartin Matuska if (fe->fe_logical == 0 && length == size) {
9216c95142eSMartin Matuska /* This is not sparse. */
9226c95142eSMartin Matuska do_fiemap = 0;
9236c95142eSMartin Matuska break;
9246c95142eSMartin Matuska }
9256c95142eSMartin Matuska if (length > 0)
9266c95142eSMartin Matuska archive_entry_sparse_add_entry(entry,
9276c95142eSMartin Matuska fe->fe_logical, length);
9286c95142eSMartin Matuska }
9296c95142eSMartin Matuska if (fe->fe_flags & FIEMAP_EXTENT_LAST)
9306c95142eSMartin Matuska do_fiemap = 0;
9316c95142eSMartin Matuska }
9326c95142eSMartin Matuska if (do_fiemap) {
9336c95142eSMartin Matuska fe = fm->fm_extents + fm->fm_mapped_extents -1;
9346c95142eSMartin Matuska fm->fm_start = fe->fe_logical + fe->fe_length;
9356c95142eSMartin Matuska } else
9366c95142eSMartin Matuska break;
9376c95142eSMartin Matuska }
938d5d08d29SMartin Matuska exit_setup_sparse_fiemap:
9396c95142eSMartin Matuska return (exit_sts);
9406c95142eSMartin Matuska }
9416c95142eSMartin Matuska
942d5d08d29SMartin Matuska #if !defined(SEEK_HOLE) || !defined(SEEK_DATA)
943d5d08d29SMartin Matuska static int
setup_sparse(struct archive_read_disk * a,struct archive_entry * entry,int * fd)944d5d08d29SMartin Matuska setup_sparse(struct archive_read_disk *a,
945d5d08d29SMartin Matuska struct archive_entry *entry, int *fd)
946d5d08d29SMartin Matuska {
947d5d08d29SMartin Matuska return setup_sparse_fiemap(a, entry, fd);
948d5d08d29SMartin Matuska }
949d5d08d29SMartin Matuska #endif
950d5d08d29SMartin Matuska #endif /* defined(HAVE_LINUX_FIEMAP_H) */
951d5d08d29SMartin Matuska
952d5d08d29SMartin Matuska #if defined(SEEK_HOLE) && defined(SEEK_DATA)
9536c95142eSMartin Matuska
9546c95142eSMartin Matuska /*
955d5d08d29SMartin Matuska * SEEK_HOLE sparse interface (FreeBSD, Linux, Solaris)
9566c95142eSMartin Matuska */
9576c95142eSMartin Matuska
9586c95142eSMartin Matuska static int
setup_sparse(struct archive_read_disk * a,struct archive_entry * entry,int * fd)9596c95142eSMartin Matuska setup_sparse(struct archive_read_disk *a,
960fd082e96SMartin Matuska struct archive_entry *entry, int *fd)
9616c95142eSMartin Matuska {
9626c95142eSMartin Matuska int64_t size;
963d5d08d29SMartin Matuska off_t initial_off;
964d5d08d29SMartin Matuska off_t off_s, off_e;
9656c95142eSMartin Matuska int exit_sts = ARCHIVE_OK;
966cdf63a70SMartin Matuska int check_fully_sparse = 0;
9674657548dSMartin Matuska const char *path;
9686c95142eSMartin Matuska
9696c95142eSMartin Matuska if (archive_entry_filetype(entry) != AE_IFREG
9706c95142eSMartin Matuska || archive_entry_size(entry) <= 0
9716c95142eSMartin Matuska || archive_entry_hardlink(entry) != NULL)
9726c95142eSMartin Matuska return (ARCHIVE_OK);
9736c95142eSMartin Matuska
9746c95142eSMartin Matuska /* Does filesystem support the reporting of hole ? */
975a8fc61d5SMartin Matuska if (*fd < 0)
9764657548dSMartin Matuska path = archive_read_disk_entry_setup_path(a, entry, fd);
977a8fc61d5SMartin Matuska else
978a8fc61d5SMartin Matuska path = NULL;
979fd082e96SMartin Matuska
980fd082e96SMartin Matuska if (*fd >= 0) {
981d5d08d29SMartin Matuska #ifdef _PC_MIN_HOLE_SIZE
982fd082e96SMartin Matuska if (fpathconf(*fd, _PC_MIN_HOLE_SIZE) <= 0)
9836c95142eSMartin Matuska return (ARCHIVE_OK);
984d5d08d29SMartin Matuska #endif
985fd082e96SMartin Matuska initial_off = lseek(*fd, 0, SEEK_CUR);
9866c95142eSMartin Matuska if (initial_off != 0)
987fd082e96SMartin Matuska lseek(*fd, 0, SEEK_SET);
9886c95142eSMartin Matuska } else {
989a8fc61d5SMartin Matuska if (path == NULL)
990a8fc61d5SMartin Matuska return (ARCHIVE_FAILED);
991d5d08d29SMartin Matuska #ifdef _PC_MIN_HOLE_SIZE
9926c95142eSMartin Matuska if (pathconf(path, _PC_MIN_HOLE_SIZE) <= 0)
9936c95142eSMartin Matuska return (ARCHIVE_OK);
994d5d08d29SMartin Matuska #endif
995acc60b03SMartin Matuska *fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
996fd082e96SMartin Matuska if (*fd < 0) {
9976c95142eSMartin Matuska archive_set_error(&a->archive, errno,
9986c95142eSMartin Matuska "Can't open `%s'", path);
9996c95142eSMartin Matuska return (ARCHIVE_FAILED);
10006c95142eSMartin Matuska }
1001acc60b03SMartin Matuska __archive_ensure_cloexec_flag(*fd);
10026c95142eSMartin Matuska initial_off = 0;
10036c95142eSMartin Matuska }
10046c95142eSMartin Matuska
1005d5d08d29SMartin Matuska #ifndef _PC_MIN_HOLE_SIZE
1006d5d08d29SMartin Matuska /* Check if the underlying filesystem supports seek hole */
1007d5d08d29SMartin Matuska off_s = lseek(*fd, 0, SEEK_HOLE);
1008d5d08d29SMartin Matuska if (off_s < 0)
1009d5d08d29SMartin Matuska #if defined(HAVE_LINUX_FIEMAP_H)
1010d5d08d29SMartin Matuska return setup_sparse_fiemap(a, entry, fd);
1011d5d08d29SMartin Matuska #else
1012d5d08d29SMartin Matuska goto exit_setup_sparse;
1013d5d08d29SMartin Matuska #endif
1014d5d08d29SMartin Matuska else if (off_s > 0)
1015d5d08d29SMartin Matuska lseek(*fd, 0, SEEK_SET);
1016d5d08d29SMartin Matuska #endif
1017d5d08d29SMartin Matuska
10186c95142eSMartin Matuska off_s = 0;
10196c95142eSMartin Matuska size = archive_entry_size(entry);
10206c95142eSMartin Matuska while (off_s < size) {
1021fd082e96SMartin Matuska off_s = lseek(*fd, off_s, SEEK_DATA);
10226c95142eSMartin Matuska if (off_s == (off_t)-1) {
1023cdf63a70SMartin Matuska if (errno == ENXIO) {
1024cdf63a70SMartin Matuska /* no more hole */
1025cdf63a70SMartin Matuska if (archive_entry_sparse_count(entry) == 0) {
1026cdf63a70SMartin Matuska /* Potentially a fully-sparse file. */
1027cdf63a70SMartin Matuska check_fully_sparse = 1;
1028cdf63a70SMartin Matuska }
1029cdf63a70SMartin Matuska break;
1030cdf63a70SMartin Matuska }
10316c95142eSMartin Matuska archive_set_error(&a->archive, errno,
10326c95142eSMartin Matuska "lseek(SEEK_HOLE) failed");
10336c95142eSMartin Matuska exit_sts = ARCHIVE_FAILED;
10346c95142eSMartin Matuska goto exit_setup_sparse;
10356c95142eSMartin Matuska }
1036fd082e96SMartin Matuska off_e = lseek(*fd, off_s, SEEK_HOLE);
1037fd082e96SMartin Matuska if (off_e == (off_t)-1) {
10386c95142eSMartin Matuska if (errno == ENXIO) {
1039fd082e96SMartin Matuska off_e = lseek(*fd, 0, SEEK_END);
10406c95142eSMartin Matuska if (off_e != (off_t)-1)
10416c95142eSMartin Matuska break;/* no more data */
10426c95142eSMartin Matuska }
10436c95142eSMartin Matuska archive_set_error(&a->archive, errno,
10446c95142eSMartin Matuska "lseek(SEEK_DATA) failed");
10456c95142eSMartin Matuska exit_sts = ARCHIVE_FAILED;
10466c95142eSMartin Matuska goto exit_setup_sparse;
10476c95142eSMartin Matuska }
10486c95142eSMartin Matuska if (off_s == 0 && off_e == size)
1049a2e802b7SMartin Matuska break;/* This is not sparse. */
10506c95142eSMartin Matuska archive_entry_sparse_add_entry(entry, off_s,
10516c95142eSMartin Matuska off_e - off_s);
10526c95142eSMartin Matuska off_s = off_e;
10536c95142eSMartin Matuska }
1054cdf63a70SMartin Matuska
1055cdf63a70SMartin Matuska if (check_fully_sparse) {
1056cdf63a70SMartin Matuska if (lseek(*fd, 0, SEEK_HOLE) == 0 &&
1057cdf63a70SMartin Matuska lseek(*fd, 0, SEEK_END) == size) {
1058cdf63a70SMartin Matuska /* Fully sparse file; insert a zero-length "data" entry */
1059cdf63a70SMartin Matuska archive_entry_sparse_add_entry(entry, 0, 0);
1060cdf63a70SMartin Matuska }
1061cdf63a70SMartin Matuska }
10626c95142eSMartin Matuska exit_setup_sparse:
1063fd082e96SMartin Matuska lseek(*fd, initial_off, SEEK_SET);
10646c95142eSMartin Matuska return (exit_sts);
10656c95142eSMartin Matuska }
10666c95142eSMartin Matuska
1067d5d08d29SMartin Matuska #elif !defined(HAVE_LINUX_FIEMAP_H)
10686c95142eSMartin Matuska
10696c95142eSMartin Matuska /*
10706c95142eSMartin Matuska * Generic (stub) sparse support.
10716c95142eSMartin Matuska */
10726c95142eSMartin Matuska static int
setup_sparse(struct archive_read_disk * a,struct archive_entry * entry,int * fd)10736c95142eSMartin Matuska setup_sparse(struct archive_read_disk *a,
1074fd082e96SMartin Matuska struct archive_entry *entry, int *fd)
10756c95142eSMartin Matuska {
10766c95142eSMartin Matuska (void)a; /* UNUSED */
10776c95142eSMartin Matuska (void)entry; /* UNUSED */
10786c95142eSMartin Matuska (void)fd; /* UNUSED */
10796c95142eSMartin Matuska return (ARCHIVE_OK);
10806c95142eSMartin Matuska }
10816c95142eSMartin Matuska
10826c95142eSMartin Matuska #endif
10836c95142eSMartin Matuska
10846c95142eSMartin Matuska #endif /* !defined(_WIN32) || defined(__CYGWIN__) */
10856c95142eSMartin Matuska
1086