From eee068778cb28ecf3c14e1bf843a95547d72c42d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:14:06 +0200 Subject: Adding upstream version 2.2.40. Signed-off-by: Daniel Baumann --- tools/gpgtar-create.c | 1297 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1297 insertions(+) create mode 100644 tools/gpgtar-create.c (limited to 'tools/gpgtar-create.c') diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c new file mode 100644 index 0000000..e642da0 --- /dev/null +++ b/tools/gpgtar-create.c @@ -0,0 +1,1297 @@ +/* gpgtar-create.c - Create a TAR archive + * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH + * Copyright (C) 2010, 2012, 2013 Werner Koch + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include +#else /*!HAVE_W32_SYSTEM*/ +# include +# include +#endif /*!HAVE_W32_SYSTEM*/ + +#include "../common/i18n.h" +#include +#include "../common/exechelp.h" +#include "../common/sysutils.h" +#include "../common/ccparray.h" +#include "../common/membuf.h" +#include "gpgtar.h" + +#ifndef HAVE_LSTAT +#define lstat(a,b) gnupg_stat ((a), (b)) +#endif + + +/* Count the number of written headers. Extended headers are not + * counted. */ +static unsigned long global_header_count; + + +/* Object to control the file scanning. */ +struct scanctrl_s; +typedef struct scanctrl_s *scanctrl_t; +struct scanctrl_s +{ + tar_header_t flist; + tar_header_t *flist_tail; + int nestlevel; +}; + + + +/* On Windows convert name to UTF8 and return it; caller must release + * the result. On Unix or if ALREADY_UTF8 is set, this function is a + * mere xtrystrcopy. On failure NULL is returned and ERRNO set. */ +static char * +name_to_utf8 (const char *name, int already_utf8) +{ +#ifdef HAVE_W32_SYSTEM + wchar_t *wstring; + char *result; + + if (already_utf8) + result = xtrystrdup (name); + else + { + wstring = native_to_wchar (name); + if (!wstring) + return NULL; + result = wchar_to_utf8 (wstring); + xfree (wstring); + } + return result; + +#else /*!HAVE_W32_SYSTEM */ + + (void)already_utf8; + return xtrystrdup (name); + +#endif /*!HAVE_W32_SYSTEM */ +} + + + + +/* Given a fresh header object HDR with only the name field set, try + to gather all available info. This is the W32 version. */ +#ifdef HAVE_W32_SYSTEM +static gpg_error_t +fillup_entry_w32 (tar_header_t hdr) +{ + char *p; + wchar_t *wfname; + WIN32_FILE_ATTRIBUTE_DATA fad; + DWORD attr; + + for (p=hdr->name; *p; p++) + if (*p == '/') + *p = '\\'; + wfname = gpgrt_fname_to_wchar (hdr->name); + for (p=hdr->name; *p; p++) + if (*p == '\\') + *p = '/'; + if (!wfname) + { + log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1)); + return gpg_error_from_syserror (); + } + if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) + { + log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1)); + xfree (wfname); + return gpg_error_from_syserror (); + } + xfree (wfname); + + attr = fad.dwFileAttributes; + + if ((attr & FILE_ATTRIBUTE_NORMAL)) + hdr->typeflag = TF_REGULAR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) + hdr->typeflag = TF_DIRECTORY; + else if ((attr & FILE_ATTRIBUTE_DEVICE)) + hdr->typeflag = TF_NOTSUP; + else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) + hdr->typeflag = TF_NOTSUP; + else + hdr->typeflag = TF_REGULAR; + + /* Map some attributes to USTAR defined mode bits. */ + hdr->mode = 0640; /* User may read and write, group only read. */ + if ((attr & FILE_ATTRIBUTE_DIRECTORY)) + hdr->mode |= 0110; /* Dirs are user and group executable. */ + if ((attr & FILE_ATTRIBUTE_READONLY)) + hdr->mode &= ~0200; /* Clear the user write bit. */ + if ((attr & FILE_ATTRIBUTE_HIDDEN)) + hdr->mode &= ~0707; /* Clear all user and other bits. */ + if ((attr & FILE_ATTRIBUTE_SYSTEM)) + hdr->mode |= 0004; /* Make it readable by other. */ + + /* Only set the size for a regular file. */ + if (hdr->typeflag == TF_REGULAR) + hdr->size = (fad.nFileSizeHigh * ((unsigned long long)MAXDWORD+1) + + fad.nFileSizeLow); + + hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) + | fad.ftLastWriteTime.dwLowDateTime); + if (!hdr->mtime) + hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) + | fad.ftCreationTime.dwLowDateTime); + hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ + hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ + + return 0; +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Given a fresh header object HDR with only the name field set, try + to gather all available info. This is the POSIX version. */ +#ifndef HAVE_W32_SYSTEM +static gpg_error_t +fillup_entry_posix (tar_header_t hdr) +{ + gpg_error_t err; + struct stat sbuf; + + if (lstat (hdr->name, &sbuf)) + { + err = gpg_error_from_syserror (); + log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err)); + return err; + } + + if (S_ISREG (sbuf.st_mode)) + hdr->typeflag = TF_REGULAR; + else if (S_ISDIR (sbuf.st_mode)) + hdr->typeflag = TF_DIRECTORY; + else if (S_ISCHR (sbuf.st_mode)) + hdr->typeflag = TF_CHARDEV; + else if (S_ISBLK (sbuf.st_mode)) + hdr->typeflag = TF_BLOCKDEV; + else if (S_ISFIFO (sbuf.st_mode)) + hdr->typeflag = TF_FIFO; + else if (S_ISLNK (sbuf.st_mode)) + hdr->typeflag = TF_SYMLINK; + else + hdr->typeflag = TF_NOTSUP; + + /* FIXME: Save DEV and INO? */ + + /* Set the USTAR defined mode bits using the system macros. */ + if (sbuf.st_mode & S_IRUSR) + hdr->mode |= 0400; + if (sbuf.st_mode & S_IWUSR) + hdr->mode |= 0200; + if (sbuf.st_mode & S_IXUSR) + hdr->mode |= 0100; + if (sbuf.st_mode & S_IRGRP) + hdr->mode |= 0040; + if (sbuf.st_mode & S_IWGRP) + hdr->mode |= 0020; + if (sbuf.st_mode & S_IXGRP) + hdr->mode |= 0010; + if (sbuf.st_mode & S_IROTH) + hdr->mode |= 0004; + if (sbuf.st_mode & S_IWOTH) + hdr->mode |= 0002; + if (sbuf.st_mode & S_IXOTH) + hdr->mode |= 0001; +#ifdef S_IXUID + if (sbuf.st_mode & S_IXUID) + hdr->mode |= 04000; +#endif +#ifdef S_IXGID + if (sbuf.st_mode & S_IXGID) + hdr->mode |= 02000; +#endif +#ifdef S_ISVTX + if (sbuf.st_mode & S_ISVTX) + hdr->mode |= 01000; +#endif + + hdr->nlink = sbuf.st_nlink; + + hdr->uid = sbuf.st_uid; + hdr->gid = sbuf.st_gid; + + /* Only set the size for a regular file. */ + if (hdr->typeflag == TF_REGULAR) + hdr->size = sbuf.st_size; + + hdr->mtime = sbuf.st_mtime; + + return 0; +} +#endif /*!HAVE_W32_SYSTEM*/ + + +/* Add a new entry. The name of a directory entry is ENTRYNAME; if + that is NULL, DNAME is the name of the directory itself. Under + Windows ENTRYNAME shall have backslashes replaced by standard + slashes. */ +static gpg_error_t +add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) +{ + gpg_error_t err; + tar_header_t hdr; + char *p; + size_t dnamelen = strlen (dname); + + log_assert (dnamelen); + + hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 + + (entryname? strlen (entryname) : 0) + 1); + if (!hdr) + return gpg_error_from_syserror (); + + p = stpcpy (hdr->name, dname); + if (entryname) + { + if (dname[dnamelen-1] != '/') + *p++ = '/'; + strcpy (p, entryname); + } + else + { + if (hdr->name[dnamelen-1] == '/') + hdr->name[dnamelen-1] = 0; + } +#ifdef HAVE_DOSISH_SYSTEM + err = fillup_entry_w32 (hdr); +#else + err = fillup_entry_posix (hdr); +#endif + if (err) + xfree (hdr); + else + { + /* FIXME: We don't have the extended info yet available so we + * can't print them. */ + if (opt.verbose) + gpgtar_print_header (hdr, NULL, log_get_stream ()); + *scanctrl->flist_tail = hdr; + scanctrl->flist_tail = &hdr->next; + } + + return 0; +} + + +static gpg_error_t +scan_directory (const char *dname, scanctrl_t scanctrl) +{ + gpg_error_t err = 0; + +#ifdef HAVE_W32_SYSTEM + /* Note that we introduced gnupg_opendir only after we had deployed + * this code and thus we don't change it for now. */ + WIN32_FIND_DATAW fi; + HANDLE hd = INVALID_HANDLE_VALUE; + char *p; + + if (!*dname) + return 0; /* An empty directory name has no entries. */ + + { + char *fname; + wchar_t *wfname; + + fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (!strcmp (dname, "/")) + strcpy (fname, "/*"); /* Trailing slash is not allowed. */ + else if (!strcmp (dname, ".")) + strcpy (fname, "*"); + else if (*dname && dname[strlen (dname)-1] == '/') + strcpy (stpcpy (fname, dname), "*"); + else if (*dname && dname[strlen (dname)-1] != '*') + strcpy (stpcpy (fname, dname), "/*"); + else + strcpy (fname, dname); + + for (p=fname; *p; p++) + if (*p == '/') + *p = '\\'; + wfname = gpgrt_fname_to_wchar (fname); + xfree (fname); + if (!wfname) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, gpg_strerror (err)); + goto leave; + } + hd = FindFirstFileW (wfname, &fi); + if (hd == INVALID_HANDLE_VALUE) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, w32_strerror (-1)); + xfree (wfname); + goto leave; + } + xfree (wfname); + } + + do + { + char *fname = wchar_to_utf8 (fi.cFileName); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("error converting filename: %s\n", w32_strerror (-1)); + break; + } + for (p=fname; *p; p++) + if (*p == '\\') + *p = '/'; + if (!strcmp (fname, "." ) || !strcmp (fname, "..")) + err = 0; /* Skip self and parent dir entry. */ + else if (!strncmp (dname, "./", 2) && dname[2]) + err = add_entry (dname+2, fname, scanctrl); + else + err = add_entry (dname, fname, scanctrl); + xfree (fname); + } + while (!err && FindNextFileW (hd, &fi)); + if (err) + ; + else if (GetLastError () == ERROR_NO_MORE_FILES) + err = 0; + else + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, w32_strerror (-1)); + } + + leave: + if (hd != INVALID_HANDLE_VALUE) + FindClose (hd); + +#else /*!HAVE_W32_SYSTEM*/ + DIR *dir; + struct dirent *de; + + if (!*dname) + return 0; /* An empty directory name has no entries. */ + + dir = opendir (dname); + if (!dir) + { + err = gpg_error_from_syserror (); + log_error (_("error reading directory '%s': %s\n"), + dname, gpg_strerror (err)); + return err; + } + + while ((de = readdir (dir))) + { + if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) + continue; /* Skip self and parent dir entry. */ + + err = add_entry (dname, de->d_name, scanctrl); + if (err) + goto leave; + } + + leave: + closedir (dir); +#endif /*!HAVE_W32_SYSTEM*/ + return err; +} + + +static gpg_error_t +scan_recursive (const char *dname, scanctrl_t scanctrl) +{ + gpg_error_t err = 0; + tar_header_t hdr, *start_tail, *stop_tail; + + if (scanctrl->nestlevel > 200) + { + log_error ("directories too deeply nested\n"); + return gpg_error (GPG_ERR_RESOURCE_LIMIT); + } + scanctrl->nestlevel++; + + log_assert (scanctrl->flist_tail); + start_tail = scanctrl->flist_tail; + scan_directory (dname, scanctrl); + stop_tail = scanctrl->flist_tail; + hdr = *start_tail; + for (; hdr && hdr != *stop_tail; hdr = hdr->next) + if (hdr->typeflag == TF_DIRECTORY) + { + if (opt.verbose > 1) + log_info ("scanning directory '%s'\n", hdr->name); + scan_recursive (hdr->name, scanctrl); + } + + scanctrl->nestlevel--; + return err; +} + + +/* Returns true if PATTERN is acceptable. */ +static int +pattern_valid_p (const char *pattern) +{ + if (!*pattern) + return 0; + if (*pattern == '.' && pattern[1] == '.') + return 0; + if (*pattern == '/' +#ifdef HAVE_DOSISH_SYSTEM + || *pattern == '\\' +#endif + ) + return 0; /* Absolute filenames are not supported. */ +#ifdef HAVE_DRIVE_LETTERS + if (((*pattern >= 'a' && *pattern <= 'z') + || (*pattern >= 'A' && *pattern <= 'Z')) + && pattern[1] == ':') + return 0; /* Drive letter are not allowed either. */ +#endif /*HAVE_DRIVE_LETTERS*/ + + return 1; /* Okay. */ +} + + + +static void +store_xoctal (char *buffer, size_t length, unsigned long long value) +{ + char *p, *pend; + size_t n; + unsigned long long v; + + log_assert (length > 1); + + v = value; + n = length; + p = pend = buffer + length; + *--p = 0; /* Nul byte. */ + n--; + do + { + *--p = '0' + (v % 8); + v /= 8; + n--; + } + while (v && n); + if (!v) + { + /* Pad. */ + for ( ; n; n--) + *--p = '0'; + } + else /* Does not fit into the field. Store as binary number. */ + { + v = value; + n = length; + p = pend = buffer + length; + do + { + *--p = v; + v /= 256; + n--; + } + while (v && n); + if (!v) + { + /* Pad. */ + for ( ; n; n--) + *--p = 0; + if (*p & 0x80) + BUG (); + *p |= 0x80; /* Set binary flag. */ + } + else + BUG (); + } +} + + +static void +store_uname (char *buffer, size_t length, unsigned long uid) +{ + static int initialized; + static unsigned long lastuid; + static char lastuname[32]; + + if (!initialized || uid != lastuid) + { +#ifdef HAVE_W32_SYSTEM + mem2str (lastuname, uid? "user":"root", sizeof lastuname); +#else + struct passwd *pw = getpwuid (uid); + + lastuid = uid; + initialized = 1; + if (pw) + mem2str (lastuname, pw->pw_name, sizeof lastuname); + else + { + log_info ("failed to get name for uid %lu\n", uid); + *lastuname = 0; + } +#endif + } + mem2str (buffer, lastuname, length); +} + + +static void +store_gname (char *buffer, size_t length, unsigned long gid) +{ + static int initialized; + static unsigned long lastgid; + static char lastgname[32]; + + if (!initialized || gid != lastgid) + { +#ifdef HAVE_W32_SYSTEM + mem2str (lastgname, gid? "users":"root", sizeof lastgname); +#else + struct group *gr = getgrgid (gid); + + lastgid = gid; + initialized = 1; + if (gr) + mem2str (lastgname, gr->gr_name, sizeof lastgname); + else + { + log_info ("failed to get name for gid %lu\n", gid); + *lastgname = 0; + } +#endif + } + mem2str (buffer, lastgname, length); +} + + +static void +compute_checksum (void *record) +{ + struct ustar_raw_header *raw = record; + unsigned long chksum = 0; + unsigned char *p; + size_t n; + + memset (raw->checksum, ' ', sizeof raw->checksum); + p = record; + for (n=0; n < RECORDSIZE; n++) + chksum += *p++; + store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); + raw->checksum[7] = ' '; +} + + + +/* Read a symlink without truncating it. Caller must release the + * returned buffer. Returns NULL on error. */ +#ifndef HAVE_W32_SYSTEM +static char * +myreadlink (const char *name) +{ + char *buffer; + size_t size; + int nread; + + for (size = 1024; size <= 65536; size *= 2) + { + buffer = xtrymalloc (size); + if (!buffer) + return NULL; + + nread = readlink (name, buffer, size - 1); + if (nread < 0) + { + xfree (buffer); + return NULL; + } + if (nread < size - 1) + { + buffer[nread] = 0; + return buffer; /* Got it. */ + } + + xfree (buffer); + } + gpg_err_set_errno (ERANGE); + return NULL; +} +#endif /*Unix*/ + + + +/* Build a header. If the filename or the link name ist too long + * allocate an exthdr and use a replacement file name in RECORD. + * Caller should always release R_EXTHDR; this function initializes it + * to point to NULL. */ +static gpg_error_t +build_header (void *record, tar_header_t hdr, strlist_t *r_exthdr) +{ + gpg_error_t err; + struct ustar_raw_header *raw = record; + size_t namelen, n; + strlist_t sl; + + memset (record, 0, RECORDSIZE); + *r_exthdr = NULL; + + /* Store name and prefix. */ + namelen = strlen (hdr->name); + if (namelen < sizeof raw->name) + memcpy (raw->name, hdr->name, namelen); + else + { + n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; + for (n--; n ; n--) + if (hdr->name[n] == '/') + break; + if (namelen - n < sizeof raw->name) + { + /* Note that the N is < sizeof prefix and that the + delimiting slash is not stored. */ + memcpy (raw->prefix, hdr->name, n); + memcpy (raw->name, hdr->name+n+1, namelen - n); + } + else + { + /* Too long - prepare extended header. */ + sl = add_to_strlist_try (r_exthdr, hdr->name); + if (!sl) + { + err = gpg_error_from_syserror (); + log_error ("error storing file '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + sl->flags = 1; /* Mark as path */ + /* The name we use is not POSIX compliant but because we + * expect that (for security issues) a tarball will anyway + * be extracted to a unique new directory, a simple counter + * will do. To ease testing we also put in the PID. The + * count is bumped after the header has been written. */ + snprintf (raw->name, sizeof raw->name-1, "_@paxheader.%u.%lu", + (unsigned int)getpid(), global_header_count + 1); + } + } + + store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); + store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); + store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); + store_xoctal (raw->size, sizeof raw->size, hdr->size); + store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); + + switch (hdr->typeflag) + { + case TF_REGULAR: raw->typeflag[0] = '0'; break; + case TF_HARDLINK: raw->typeflag[0] = '1'; break; + case TF_SYMLINK: raw->typeflag[0] = '2'; break; + case TF_CHARDEV: raw->typeflag[0] = '3'; break; + case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; + case TF_DIRECTORY: raw->typeflag[0] = '5'; break; + case TF_FIFO: raw->typeflag[0] = '6'; break; + default: return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + memcpy (raw->magic, "ustar", 6); + raw->version[0] = '0'; + raw->version[1] = '0'; + + store_uname (raw->uname, sizeof raw->uname, hdr->uid); + store_gname (raw->gname, sizeof raw->gname, hdr->gid); + +#ifndef HAVE_W32_SYSTEM + if (hdr->typeflag == TF_SYMLINK) + { + int nread; + char *p; + + nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); + if (nread < 0) + { + err = gpg_error_from_syserror (); + log_error ("error reading symlink '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + raw->linkname[nread] = 0; + if (nread == sizeof raw->linkname -1) + { + /* Truncated - read again and store as extended header. */ + p = myreadlink (hdr->name); + if (!p) + { + err = gpg_error_from_syserror (); + log_error ("error reading symlink '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + + sl = add_to_strlist_try (r_exthdr, p); + xfree (p); + if (!sl) + { + err = gpg_error_from_syserror (); + log_error ("error storing syslink '%s': %s\n", + hdr->name, gpg_strerror (err)); + return err; + } + sl->flags = 2; /* Mark as linkpath */ + } + } +#endif /*!HAVE_W32_SYSTEM*/ + + compute_checksum (record); + + return 0; +} + + +/* Add an extended header record (NAME,VALUE) to the buffer MB. */ +static void +add_extended_header_record (membuf_t *mb, const char *name, const char *value) +{ + size_t n, n0, n1; + char numbuf[35]; + size_t valuelen; + + /* To avoid looping in most cases, we guess the initial value. */ + valuelen = strlen (value); + n1 = valuelen > 95? 3 : 2; + do + { + n0 = n1; + /* (3 for the space before name, the '=', and the LF.) */ + n = n0 + strlen (name) + valuelen + 3; + snprintf (numbuf, sizeof numbuf, "%zu", n); + n1 = strlen (numbuf); + } + while (n0 != n1); + put_membuf_str (mb, numbuf); + put_membuf (mb, " ", 1); + put_membuf_str (mb, name); + put_membuf (mb, "=", 1); + put_membuf (mb, value, valuelen); + put_membuf (mb, "\n", 1); +} + + + +/* Write the extended header specified by EXTHDR to STREAM. */ +static gpg_error_t +write_extended_header (estream_t stream, const void *record, strlist_t exthdr) +{ + gpg_error_t err = 0; + struct ustar_raw_header raw; + strlist_t sl; + membuf_t mb; + char *buffer, *p; + size_t buflen; + + init_membuf (&mb, 2*RECORDSIZE); + + for (sl=exthdr; sl; sl = sl->next) + { + if (sl->flags == 1) + add_extended_header_record (&mb, "path", sl->d); + else if (sl->flags == 2) + add_extended_header_record (&mb, "linkpath", sl->d); + } + + buffer = get_membuf (&mb, &buflen); + if (!buffer) + { + err = gpg_error_from_syserror (); + log_error ("error building extended header: %s\n", gpg_strerror (err)); + goto leave; + } + + /* We copy the header from the standard header record, so that an + * extracted extended header (using a non-pax aware software) is + * written with the same properties as the original file. The real + * entry will overwrite it anyway. Of course we adjust the size and + * the type. */ + memcpy (&raw, record, RECORDSIZE); + store_xoctal (raw.size, sizeof raw.size, buflen); + raw.typeflag[0] = 'x'; /* Mark as extended header. */ + compute_checksum (&raw); + + err = write_record (stream, &raw); + if (err) + goto leave; + + for (p = buffer; buflen >= RECORDSIZE; p += RECORDSIZE, buflen -= RECORDSIZE) + { + err = write_record (stream, p); + if (err) + goto leave; + } + if (buflen) + { + /* Reuse RAW for builidng the last record. */ + memcpy (&raw, p, buflen); + memset ((char*)&raw+buflen, 0, RECORDSIZE - buflen); + err = write_record (stream, &raw); + if (err) + goto leave; + } + + leave: + xfree (buffer); + return err; +} + + +static gpg_error_t +write_file (estream_t stream, tar_header_t hdr) +{ + gpg_error_t err; + char record[RECORDSIZE]; + estream_t infp; + size_t nread, nbytes; + strlist_t exthdr = NULL; + int any; + + err = build_header (record, hdr, &exthdr); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) + { + log_info ("skipping unsupported file '%s'\n", hdr->name); + err = 0; + } + return err; + } + + if (hdr->typeflag == TF_REGULAR) + { + infp = es_fopen (hdr->name, "rb,sysopen"); + if (!infp) + { + err = gpg_error_from_syserror (); + log_error ("can't open '%s': %s - skipped\n", + hdr->name, gpg_strerror (err)); + return err; + } + } + else + infp = NULL; + + if (exthdr && (err = write_extended_header (stream, record, exthdr))) + goto leave; + err = write_record (stream, record); + if (err) + goto leave; + global_header_count++; + + if (hdr->typeflag == TF_REGULAR) + { + hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; + any = 0; + while (hdr->nrecords--) + { + nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); + if (!nbytes) + nbytes = RECORDSIZE; + nread = es_fread (record, 1, nbytes, infp); + if (nread != nbytes) + { + err = gpg_error_from_syserror (); + log_error ("error reading file '%s': %s%s\n", + hdr->name, gpg_strerror (err), + any? " (file shrunk?)":""); + goto leave; + } + else if (nbytes < RECORDSIZE) + memset (record + nbytes, 0, RECORDSIZE - nbytes); + any = 1; + err = write_record (stream, record); + if (err) + goto leave; + } + nread = es_fread (record, 1, 1, infp); + if (nread) + log_info ("note: file '%s' has grown\n", hdr->name); + } + + leave: + if (err) + es_fclose (infp); + else if ((err = es_fclose (infp))) + log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err)); + + free_strlist (exthdr); + return err; +} + + +static gpg_error_t +write_eof_mark (estream_t stream) +{ + gpg_error_t err; + char record[RECORDSIZE]; + + memset (record, 0, sizeof record); + err = write_record (stream, record); + if (!err) + err = write_record (stream, record); + return err; +} + + + +/* Create a new tarball using the names in the array INPATTERN. If + INPATTERN is NULL take the pattern as null terminated strings from + stdin or from the file specified by FILES_FROM. If NULL_NAMES is + set the filenames in such a file are delimited by a binary Nul and + not by a LF. */ +gpg_error_t +gpgtar_create (char **inpattern, const char *files_from, int null_names, + int encrypt, int sign) +{ + gpg_error_t err = 0; + struct scanctrl_s scanctrl_buffer; + scanctrl_t scanctrl = &scanctrl_buffer; + tar_header_t hdr, *start_tail; + estream_t files_from_stream = NULL; + estream_t outstream = NULL; + int eof_seen = 0; + pid_t pid = (pid_t)(-1); + + memset (scanctrl, 0, sizeof *scanctrl); + scanctrl->flist_tail = &scanctrl->flist; + + /* { unsigned int cpno, cpno2, cpno3; */ + + /* cpno = GetConsoleOutputCP (); */ + /* cpno2 = GetACP (); */ + /* cpno3 = GetOEMCP (); */ + /* log_debug ("Codepages: Console: %u ANSI: %u OEM: %u\n", */ + /* cpno, cpno2, cpno3); */ + /* } */ + + + if (!inpattern) + { + if (!files_from || !strcmp (files_from, "-")) + { + files_from = "-"; + files_from_stream = es_stdin; + if (null_names) + es_set_binary (es_stdin); + } + else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r"))) + { + err = gpg_error_from_syserror (); + log_error ("error opening '%s': %s\n", + files_from, gpg_strerror (err)); + return err; + } + } + + + if (opt.directory && gnupg_chdir (opt.directory)) + { + err = gpg_error_from_syserror (); + log_error ("chdir to '%s' failed: %s\n", + opt.directory, gpg_strerror (err)); + return err; + } + + while (!eof_seen) + { + char *pat, *p; + int skip_this = 0; + + if (inpattern) + { + const char *pattern = *inpattern; + + if (!pattern) + break; /* End of array. */ + inpattern++; + + if (!*pattern) + continue; + + pat = name_to_utf8 (pattern, 0); + } + else /* Read Nul or LF delimited pattern from files_from_stream. */ + { + int c; + char namebuf[4096]; + size_t n = 0; + + for (;;) + { + if ((c = es_getc (files_from_stream)) == EOF) + { + if (es_ferror (files_from_stream)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", + files_from, gpg_strerror (err)); + goto leave; + } + c = null_names ? 0 : '\n'; + eof_seen = 1; + } + if (n >= sizeof namebuf - 1) + { + if (!skip_this) + { + skip_this = 1; + log_error ("error reading '%s': %s\n", + files_from, "filename too long"); + } + } + else + namebuf[n++] = c; + + if (null_names) + { + if (!c) + { + namebuf[n] = 0; + break; + } + } + else /* Shall be LF delimited. */ + { + if (!c) + { + if (!skip_this) + { + skip_this = 1; + log_error ("error reading '%s': %s\n", + files_from, "filename with embedded Nul"); + } + } + else if ( c == '\n' ) + { + namebuf[n] = 0; + ascii_trim_spaces (namebuf); + n = strlen (namebuf); + break; + } + } + } + + if (skip_this || n < 2) + continue; + + pat = name_to_utf8 (namebuf, opt.utf8strings); + } + + if (!pat) + { + err = gpg_error_from_syserror (); + log_error ("memory allocation problem: %s\n", gpg_strerror (err)); + goto leave; + } + for (p=pat; *p; p++) + if (*p == '\\') + *p = '/'; + + if (opt.verbose > 1) + log_info ("scanning '%s'\n", pat); + + start_tail = scanctrl->flist_tail; + if (skip_this || !pattern_valid_p (pat)) + log_error ("skipping invalid name '%s'\n", pat); + else if (!add_entry (pat, NULL, scanctrl) + && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) + scan_recursive (pat, scanctrl); + + xfree (pat); + } + + if (files_from_stream && files_from_stream != es_stdin) + es_fclose (files_from_stream); + + if (encrypt || sign) + { + strlist_t arg; + ccparray_t ccp; + const char **argv; + + /* '--encrypt' may be combined with '--symmetric', but 'encrypt' + * is set either way. Clear it if no recipients are specified. + */ + if (opt.symmetric && opt.recipients == NULL) + encrypt = 0; + + ccparray_init (&ccp, 0); + if (opt.batch) + ccparray_put (&ccp, "--batch"); + if (opt.answer_yes) + ccparray_put (&ccp, "--yes"); + if (opt.answer_no) + ccparray_put (&ccp, "--no"); + if (opt.require_compliance) + ccparray_put (&ccp, "--require-compliance"); + if (opt.status_fd != -1) + { + static char tmpbuf[40]; + + snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd); + ccparray_put (&ccp, tmpbuf); + } + + ccparray_put (&ccp, "--output"); + ccparray_put (&ccp, opt.outfile? opt.outfile : "-"); + if (encrypt) + ccparray_put (&ccp, "--encrypt"); + if (sign) + ccparray_put (&ccp, "--sign"); + if (opt.user) + { + ccparray_put (&ccp, "--local-user"); + ccparray_put (&ccp, opt.user); + } + if (opt.symmetric) + ccparray_put (&ccp, "--symmetric"); + for (arg = opt.recipients; arg; arg = arg->next) + { + ccparray_put (&ccp, "--recipient"); + ccparray_put (&ccp, arg->d); + } + for (arg = opt.gpg_arguments; arg; arg = arg->next) + ccparray_put (&ccp, arg->d); + + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gnupg_spawn_process (opt.gpg_program, argv, NULL, NULL, + (GNUPG_SPAWN_KEEP_STDOUT + | GNUPG_SPAWN_KEEP_STDERR), + &outstream, NULL, NULL, &pid); + xfree (argv); + if (err) + goto leave; + es_set_binary (outstream); + } + else if (opt.outfile) /* No crypto */ + { + if (!strcmp (opt.outfile, "-")) + outstream = es_stdout; + else + outstream = es_fopen (opt.outfile, "wb,sysopen"); + if (!outstream) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (outstream == es_stdout) + es_set_binary (es_stdout); + + } + else /* Also no crypto. */ + { + outstream = es_stdout; + es_set_binary (outstream); + } + + + for (hdr = scanctrl->flist; hdr; hdr = hdr->next) + { + err = write_file (outstream, hdr); + if (err) + goto leave; + } + err = write_eof_mark (outstream); + if (err) + goto leave; + + + if (pid != (pid_t)(-1)) + { + int exitcode; + + err = es_fclose (outstream); + outstream = NULL; + if (err) + log_error ("error closing pipe: %s\n", gpg_strerror (err)); + else + { + err = gnupg_wait_process (opt.gpg_program, pid, 1, &exitcode); + if (err) + log_error ("running %s failed (exitcode=%d): %s", + opt.gpg_program, exitcode, gpg_strerror (err)); + gnupg_release_process (pid); + pid = (pid_t)(-1); + } + } + + leave: + if (!err) + { + gpg_error_t first_err; + if (outstream != es_stdout || pid != (pid_t)(-1)) + first_err = es_fclose (outstream); + else + first_err = es_fflush (outstream); + outstream = NULL; + if (! err) + err = first_err; + } + if (err) + { + log_error ("creating tarball '%s' failed: %s\n", + opt.outfile ? opt.outfile : "-", gpg_strerror (err)); + if (outstream && outstream != es_stdout) + es_fclose (outstream); + if (opt.outfile) + gnupg_remove (opt.outfile); + } + scanctrl->flist_tail = NULL; + while ( (hdr = scanctrl->flist) ) + { + scanctrl->flist = hdr->next; + xfree (hdr); + } + return err; +} -- cgit v1.2.3