archive-tar: write extended headers for file sizes >= 8GB
[gitweb.git] / archive-tar.c
index cb99df28142f164da62f5a728eb9019e57804157..57a15406d9724e70c6dfaaf6d1c0dba64f2156ad 100644 (file)
@@ -18,6 +18,13 @@ static int tar_umask = 002;
 static int write_tar_filter_archive(const struct archiver *ar,
                                    struct archiver_args *args);
 
+/*
+ * This is the max value that a ustar size header can specify, as it is fixed
+ * at 11 octal digits. POSIX specifies that we switch to extended headers at
+ * this size.
+ */
+#define USTAR_MAX_SIZE 077777777777UL
+
 /* writes out the whole block, but only if it is full */
 static void write_if_needed(void)
 {
@@ -137,6 +144,20 @@ static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
        strbuf_addch(sb, '\n');
 }
 
+/*
+ * Like strbuf_append_ext_header, but for numeric values.
+ */
+static void strbuf_append_ext_header_uint(struct strbuf *sb,
+                                         const char *keyword,
+                                         uintmax_t value)
+{
+       char buf[40]; /* big enough for 2^128 in decimal, plus NUL */
+       int len;
+
+       len = xsnprintf(buf, sizeof(buf), "%"PRIuMAX, value);
+       strbuf_append_ext_header(sb, keyword, buf, len);
+}
+
 static unsigned int ustar_header_chksum(const struct ustar_header *header)
 {
        const unsigned char *p = (const unsigned char *)header;
@@ -208,7 +229,7 @@ static int write_tar_entry(struct archiver_args *args,
        struct ustar_header header;
        struct strbuf ext_header = STRBUF_INIT;
        unsigned int old_mode = mode;
-       unsigned long size;
+       unsigned long size, size_in_header;
        void *buffer;
        int err = 0;
 
@@ -267,7 +288,13 @@ static int write_tar_entry(struct archiver_args *args,
                        memcpy(header.linkname, buffer, size);
        }
 
-       prepare_header(args, &header, mode, size);
+       size_in_header = size;
+       if (S_ISREG(mode) && size > USTAR_MAX_SIZE) {
+               size_in_header = 0;
+               strbuf_append_ext_header_uint(&ext_header, "size", size);
+       }
+
+       prepare_header(args, &header, mode, size_in_header);
 
        if (ext_header.len > 0) {
                err = write_extended_header(args, sha1, ext_header.buf,