[PATCH 1/6] Add support for longer filenames in tar headers (up to 254 bytes).

From: Joshua Elsasser <josh(at)idealist(dot)org>
To: pgsql-hackers(at)postgresql(dot)org
Cc: Joshua Elsasser <josh(at)idealist(dot)org>
Subject: [PATCH 1/6] Add support for longer filenames in tar headers (up to 254 bytes).
Date: 2015-09-29 22:16:23
Message-ID: 1443564988-17928-2-git-send-email-josh@idealist.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

New functions tarHeaderRename() and tarHeaderGetName() are exposed to
store and retrieve the longer filenames.

tarCreateHeader() continues to limit filenames to 99 bytes to preserve
compatability with existing consumers.
---
src/include/pgtar.h | 2 +
src/port/tar.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 121 insertions(+), 15 deletions(-)

diff --git a/src/include/pgtar.h b/src/include/pgtar.h
index 906db7c..b1c68fc 100644
--- a/src/include/pgtar.h
+++ b/src/include/pgtar.h
@@ -20,4 +20,6 @@ enum tarError
};

extern enum tarError tarCreateHeader(char *h, const char *filename, const char *linktarget, size_t size, mode_t mode, uid_t uid, gid_t gid, time_t mtime);
+extern enum tarError tarHeaderRename(char *h, const char *filename);
extern int tarChecksum(char *header);
+extern size_t tarHeaderGetName(const char *h, char *buf, size_t buflen);
diff --git a/src/port/tar.c b/src/port/tar.c
index 72fd4e1..23f0201 100644
--- a/src/port/tar.c
+++ b/src/port/tar.c
@@ -45,6 +45,122 @@ tarChecksum(char *header)


/*
+ * Split a file path for use in a tar header. The return value is the second
+ * half of the path if a split is required and possible, NULL otherwise.
+ */
+static const char *
+tarSplitName(const char *filename, size_t len)
+{
+ const char *sep;
+
+ if (len <= 99)
+ sep = NULL;
+ else if ((sep = strchr(&filename[len-99], '/')) != NULL)
+ sep++;
+
+ return NULL;
+}
+
+
+/*
+ * Fill in the name and prefix fields of the tar format header pointed to by
+ * h. This should only be used when initially filling out the header.
+ */
+static enum tarError
+tarHeaderSetName(char *h, const char *filename, bool isdir, bool longname)
+{
+ const char *prefix, *base;
+ size_t len, baselen, prelen;
+
+ len = strlen(filename);
+ if (longname)
+ base = tarSplitName(filename, len);
+ else
+ base = NULL;
+ if (base == NULL)
+ {
+ prefix = "";
+ prelen = 0;
+ base = filename;
+ baselen = len;
+ }
+ else
+ {
+ prefix = filename;
+ prelen = (base - filename) - 1;
+ baselen = len - (base - filename);
+ }
+
+ /*
+ * Save room for a trailing slash if this is a directory or symlink to a
+ * directory
+ */
+ if (isdir && base[baselen-1] != '/')
+ baselen++;
+
+ if (baselen > 99 || prelen > 154)
+ return TAR_NAME_TOO_LONG;
+
+ memcpy(&h[345], prefix, prelen);
+ memset(&h[345+prelen], 0, 155 - prelen);
+ memcpy(&h[0], base, baselen);
+ memset(&h[0+baselen], 0, 100 - baselen);
+
+ /*
+ * We only support symbolic links to directories, and this is
+ * indicated in the tar format by adding a slash at the end of the
+ * name, the same as for regular directories.
+ */
+ if (isdir && base[baselen-1] != '/')
+ h[0+baselen-1] = '/';
+
+ return TAR_OK;
+}
+
+
+/*
+ * Wrapper around tarHeaderSetName() to be used when changing the name in an
+ * existing header.
+ */
+enum tarError
+tarHeaderRename(char *h, const char *filename)
+{
+ size_t len;
+ bool isdir;
+ enum tarError err;
+
+ /* If the existing name ends with a slash then this must be preserved */
+ len = strnlen(h, 100);
+ isdir = (len > 0 && h[len-1] == '/');
+
+ err = tarHeaderSetName(h, filename, isdir, true);
+
+ if (err == TAR_OK)
+ /* Recalculate checksum as per tarCreateHeader() */
+ sprintf(&h[148], "%06o ", tarChecksum(h));
+
+ return err;
+}
+
+
+/*
+ * Copy the full pathname from the tar header pointed to by h into
+ * buf. Returns the total length of the path, if this is >= len then the path
+ * has been truncated.
+ */
+size_t
+tarHeaderGetName(const char *h, char *buf, size_t buflen)
+{
+ strlcpy(buf, &h[345], buflen);
+ if (buflen && buf[0] != '\0')
+ strlcat(buf, "/", buflen);
+ strlcat(buf, &h[0], buflen);
+
+ return (strlen(&h[345]) + 1 + strlen(&h[0]));
+}
+
+
+/*
* Fill in the buffer pointed to by h with a tar format header. This buffer
* must always have space for 512 characters, which is a requirement by
* the tar format.
@@ -68,20 +184,8 @@ tarCreateHeader(char *h, const char *filename, const char *linktarget,
memset(h, 0, 512); /* assume tar header size */

/* Name 100 */
- strlcpy(&h[0], filename, 100);
- if (linktarget != NULL || S_ISDIR(mode))
- {
- /*
- * We only support symbolic links to directories, and this is
- * indicated in the tar format by adding a slash at the end of the
- * name, the same as for regular directories.
- */
- int flen = strlen(filename);
-
- flen = Min(flen, 99);
- h[flen] = '/';
- h[flen + 1] = '\0';
- }
+ tarHeaderSetName(h, filename,
+ (linktarget != NULL || S_ISDIR(mode)), false);

/* Mode 8 - this doesn't include the file type bits (S_IFMT) */
sprintf(&h[100], "%07o ", (int) (mode & 07777));
@@ -139,7 +243,7 @@ tarCreateHeader(char *h, const char *filename, const char *linktarget,
/* Minor Dev 8 */
sprintf(&h[337], "%07o ", 0);

- /* Prefix 155 - not used, leave as nulls */
+ /* Prefix 155 - not currently used, leave as nulls */

/*
* We mustn't overwrite the next field while inserting the checksum.
--
2.3.0

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Joshua Elsasser 2015-09-29 22:16:24 [PATCH 2/6] pg_basebackup: factor out tar open/close code for reusability.
Previous Message Joshua Elsasser 2015-09-29 22:16:22 Re: Add pg_basebackup single tar output format