Task 727: .TGZ File Format

Task 727: .TGZ File Format

File Format Specifications for .TGZ

The .TGZ file format is a shorthand extension for a TAR archive compressed using the GZIP algorithm, equivalent to .tar.gz. It combines the TAR format for archiving (as defined in POSIX.1-1988 and extended in POSIX.1-2001, with GNU extensions) and the GZIP format for compression (as specified in RFC 1952). The TAR component handles the archiving of files and directories with their metadata, while GZIP applies lossless compression to the entire TAR stream.

  1. List of Properties Intrinsic to the File Format

The properties intrinsic to the .TGZ file format relate to its structure as a compressed archive preserving file system attributes. These include fields from the GZIP header (for the compressed container) and the TAR headers (for each archived entry, capturing file system metadata such as permissions and ownership). The following is a comprehensive list:

GZIP Header Properties (applicable to the overall file):

  • Identification bytes (ID1: 0x1F, ID2: 0x8B)
  • Compression method (typically 8 for DEFLATE)
  • Flags (bitmask for text hint, header CRC, extra fields, original name, comment)
  • Modification time (4-byte Unix timestamp)
  • Extra flags (e.g., 2 for maximum compression, 4 for fastest algorithm)
  • Operating system (e.g., 0 for FAT, 3 for Unix)
  • Optional extra field (length-prefixed data if flag set)
  • Optional original filename (null-terminated string if flag set)
  • Optional comment (null-terminated string if flag set)
  • Optional header CRC16 (2 bytes if flag set)
  • Trailing CRC32 (4 bytes, checksum of uncompressed data)
  • Trailing input size (4 bytes, uncompressed size modulo 2^32)

TAR Entry Properties (per archived file or directory, in 512-byte headers):

  • Filename (100 bytes, null-terminated)
  • Mode (8 bytes, octal permissions and file type bits)
  • Owner UID (8 bytes, octal numeric user ID)
  • Group GID (8 bytes, octal numeric group ID)
  • Size (12 bytes, octal content length in bytes)
  • Modification time (12 bytes, octal Unix timestamp)
  • Checksum (8 bytes, octal sum of header bytes)
  • Type flag (1 byte, e.g., '0' for regular file, '5' for directory, '2' for symlink)
  • Link name (100 bytes, null-terminated target if link)
  • Magic (6 bytes, "ustar\0" for POSIX compliance)
  • Version (2 bytes, "00" for ustar)
  • Owner username (32 bytes, null-terminated)
  • Group name (32 bytes, null-terminated)
  • Device major number (8 bytes, octal, for device files)
  • Device minor number (8 bytes, octal, for device files)
  • Filename prefix (155 bytes, for long names)
  • Padding (to align to 512 bytes)

These properties preserve file system attributes like ownership, permissions, and timestamps during archiving and compression.

  1. Two Direct Download Links for .TGZ Files

Note that .TGZ and .tar.gz are interchangeable extensions. The following are direct links to example files:

  1. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .TGZ Property Dump

The following is a self-contained HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post (or any HTML page). It allows users to drag and drop a .TGZ file, parses the GZIP header, decompresses using the browser's DecompressionStream (available in modern browsers like Chrome), parses TAR headers, and dumps all properties to the screen. It handles reading as ArrayBuffer and assumes ustar format.

Drag and drop a .TGZ file here
  1. Python Class for .TGZ Handling

The following Python class uses built-in modules (gzip and struct) to open, decode, read, write, and print properties. It parses headers low-level where possible.

import gzip
import struct
import os
import time

class TGZHandler:
    def __init__(self, filename=None):
        self.filename = filename
        self.gzip_props = {}
        self.tar_entries = []

    def read(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
        # Parse GZIP header
        id1, id2, cm, flg, mtime, xfl, os_type = struct.unpack('<BBBBI BB', data[:10])
        self.gzip_props = {
            'ID1': id1, 'ID2': id2, 'CM': cm, 'FLG': flg, 'MTIME': mtime,
            'XFL': xfl, 'OS': os_type
        }
        pos = 10
        if flg & 4:  # FEXTRA
            xlen = struct.unpack('<H', data[pos:pos+2])[0]
            self.gzip_props['Extra Length'] = xlen
            pos += 2 + xlen
        if flg & 8:  # FNAME
            fname_end = data.find(b'\0', pos)
            self.gzip_props['Original Filename'] = data[pos:fname_end].decode()
            pos = fname_end + 1
        if flg & 16:  # FCOMMENT
            comment_end = data.find(b'\0', pos)
            self.gzip_props['Comment'] = data[pos:comment_end].decode()
            pos = comment_end + 1
        if flg & 2:  # FHCRC
            hcrc = struct.unpack('<H', data[pos:pos+2])[0]
            self.gzip_props['Header CRC'] = hcrc
            pos += 2
        # Decompress
        with gzip.GzipFile(fileobj=open(self.filename, 'rb')) as gz:
            decompressed = gz.read()
        # Parse TAR
        tar_pos = 0
        while tar_pos < len(decompressed):
            header = decompressed[tar_pos:tar_pos+512]
            if all(b == 0 for b in header): break
            name = header[0:100].rstrip(b'\0').decode()
            if not name: break
            mode = int(header[100:108].rstrip(b'\0'), 8)
            uid = int(header[108:116].rstrip(b'\0'), 8)
            gid = int(header[116:124].rstrip(b'\0'), 8)
            size = int(header[124:136].rstrip(b'\0'), 8)
            mtime = int(header[136:148].rstrip(b'\0'), 8)
            chksum = int(header[148:156].rstrip(b'\0'), 8)
            typeflag = chr(header[156])
            linkname = header[157:257].rstrip(b'\0').decode()
            magic = header[257:263].rstrip(b'\0').decode()
            version = header[263:265].rstrip(b'\0').decode()
            uname = header[265:297].rstrip(b'\0').decode()
            gname = header[297:329].rstrip(b'\0').decode()
            devmajor = int(header[329:337].rstrip(b'\0'), 8)
            devminor = int(header[337:345].rstrip(b'\0'), 8)
            prefix = header[345:500].rstrip(b'\0').decode()
            self.tar_entries.append({
                'Name': f"{prefix}/{name}" if prefix else name,
                'Mode': oct(mode), 'UID': uid, 'GID': gid, 'Size': size,
                'MTime': mtime, 'Checksum': chksum, 'Type': typeflag,
                'Linkname': linkname, 'Magic': magic, 'Version': version,
                'UName': uname, 'GName': gname, 'DevMajor': devmajor,
                'DevMinor': devminor
            })
            tar_pos += 512 + ((size + 511) // 512 * 512)
        # Trailing GZIP
        crc32, isize = struct.unpack('<II', data[-8:])
        self.gzip_props['CRC32'] = crc32
        self.gzip_props['ISIZE'] = isize

    def print_properties(self):
        print("GZIP Properties:")
        for key, value in self.gzip_props.items():
            print(f"- {key}: {value}")
        print("\nTAR Entry Properties:")
        for entry in self.tar_entries:
            print(f"Entry: {entry['Name']}")
            for key, value in entry.items():
                if key != 'Name':
                    print(f"  - {key}: {value}")

    def write(self, output_filename, files_to_archive):
        # Simple write: Create TAR in memory, then GZIP
        tar_data = b''
        for filepath in files_to_archive:
            with open(filepath, 'rb') as fin:
                content = fin.read()
            st = os.stat(filepath)
            name = os.path.basename(filepath).encode()[:100]
            mode = oct(st.st_mode & 0o777).encode()
            uid = oct(st.st_uid).encode()
            gid = oct(st.st_gid).encode()
            size = oct(len(content)).encode()
            mtime = oct(int(st.st_mtime)).encode()
            typeflag = b'0'  # Regular file
            linkname = b''
            magic = b'ustar\0'
            version = b'00'
            uname = os.getlogin().encode()[:32]
            gname = os.getlogin().encode()[:32]
            devmajor = b'0000000\0'
            devminor = b'0000000\0'
            prefix = b''
            header = (name.ljust(100, b'\0') + mode.rjust(7, b'0') + b'\0' + uid.rjust(7, b'0') + b'\0' +
                      gid.rjust(7, b'0') + b'\0' + size.rjust(11, b'0') + b'\0' + mtime.rjust(11, b'0') + b'\0' +
                      b'        ' + typeflag + linkname.ljust(100, b'\0') + magic + version + uname.ljust(32, b'\0') +
                      gname.ljust(32, b'\0') + devmajor + devminor + prefix.ljust(155, b'\0') + b'\0'*12)
            # Update checksum
            chksum = sum(header) & 0o77777
            header = header[:148] + oct(chksum)[2:].rjust(6, b'0').ljust(8, b' ') + header[156:]
            tar_data += header + content + b'\0' * ((512 - len(content) % 512) % 512)
        tar_data += b'\0' * 1024  # Two zero blocks
        # GZIP compress
        with gzip.open(output_filename, 'wb') as fout:
            fout.write(tar_data)

# Example usage:
# handler = TGZHandler('example.tgz')
# handler.read()
# handler.print_properties()
# handler.write('new.tgz', ['file1.txt', 'file2.txt'])
  1. Java Class for .TGZ Handling

The following Java class uses java.util.zip.GZIPInputStream for decompression and manual byte parsing for headers.

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.PosixFileAttributes;
import java.util.*;

public class TGZHandler {
    private String filename;
    private Map<String, Object> gzipProps = new HashMap<>();
    private List<Map<String, Object>> tarEntries = new ArrayList<>();

    public TGZHandler(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filename));
        // Parse GZIP header
        int pos = 0;
        int id1 = data[pos++] & 0xFF;
        int id2 = data[pos++] & 0xFF;
        int cm = data[pos++] & 0xFF;
        int flg = data[pos++] & 0xFF;
        long mtime = ((data[pos++] & 0xFF) | ((data[pos++] & 0xFF) << 8) | ((data[pos++] & 0xFF) << 16) | ((data[pos++] & 0xFFL) << 24));
        int xfl = data[pos++] & 0xFF;
        int os = data[pos++] & 0xFF;
        gzipProps.put("ID1", id1);
        gzipProps.put("ID2", id2);
        gzipProps.put("CM", cm);
        gzipProps.put("FLG", flg);
        gzipProps.put("MTIME", mtime);
        gzipProps.put("XFL", xfl);
        gzipProps.put("OS", os);

        if ((flg & 4) != 0) { // FEXTRA
            int xlen = (data[pos++] & 0xFF) | ((data[pos++] & 0xFF) << 8);
            gzipProps.put("Extra Length", xlen);
            pos += xlen;
        }
        if ((flg & 8) != 0) { // FNAME
            int start = pos;
            while (data[pos] != 0) pos++;
            gzipProps.put("Original Filename", new String(data, start, pos - start));
            pos++;
        }
        if ((flg & 16) != 0) { // FCOMMENT
            int start = pos;
            while (data[pos] != 0) pos++;
            gzipProps.put("Comment", new String(data, start, pos - start));
            pos++;
        }
        if ((flg & 2) != 0) { // FHCRC
            int hcrc = (data[pos++] & 0xFF) | ((data[pos++] & 0xFF) << 8);
            gzipProps.put("Header CRC", hcrc);
        }
        // Decompress
        ByteArrayInputStream bis = new ByteArrayInputStream(data);
        bis.skip(pos);
        GZIPInputStream gis = new GZIPInputStream(bis);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = gis.read(buffer)) > 0) {
            baos.write(buffer, 0, len);
        }
        byte[] decompressed = baos.toByteArray();
        gis.close();

        // Parse TAR
        int tarPos = 0;
        while (tarPos < decompressed.length) {
            byte[] header = Arrays.copyOfRange(decompressed, tarPos, tarPos + 512);
            boolean allZero = true;
            for (byte b : header) if (b != 0) { allZero = false; break; }
            if (allZero) break;
            String name = new String(header, 0, 100).trim();
            if (name.isEmpty()) break;
            int mode = Integer.parseInt(new String(header, 100, 8).trim(), 8);
            int uid = Integer.parseInt(new String(header, 108, 8).trim(), 8);
            int gid = Integer.parseInt(new String(header, 116, 8).trim(), 8);
            long size = Long.parseLong(new String(header, 124, 12).trim(), 8);
            long mt = Long.parseLong(new String(header, 136, 12).trim(), 8);
            int chksum = Integer.parseInt(new String(header, 148, 8).trim(), 8);
            char typeflag = (char) header[156];
            String linkname = new String(header, 157, 100).trim();
            String magic = new String(header, 257, 6).trim();
            String version = new String(header, 263, 2).trim();
            String uname = new String(header, 265, 32).trim();
            String gname = new String(header, 297, 32).trim();
            int devmajor = Integer.parseInt(new String(header, 329, 8).trim(), 8);
            int devminor = Integer.parseInt(new String(header, 337, 8).trim(), 8);
            String prefix = new String(header, 345, 155).trim();
            Map<String, Object> entry = new HashMap<>();
            entry.put("Name", prefix.isEmpty() ? name : prefix + "/" + name);
            entry.put("Mode", Integer.toOctalString(mode));
            entry.put("UID", uid);
            entry.put("GID", gid);
            entry.put("Size", size);
            entry.put("MTime", mt);
            entry.put("Checksum", chksum);
            entry.put("Type", typeflag);
            entry.put("Linkname", linkname);
            entry.put("Magic", magic);
            entry.put("Version", version);
            entry.put("UName", uname);
            entry.put("GName", gname);
            entry.put("DevMajor", devmajor);
            entry.put("DevMinor", devminor);
            tarEntries.add(entry);
            tarPos += 512 + (int) (((size + 511) / 512) * 512);
        }
        // Trailing GZIP from original data
        int end = data.length;
        long crc32 = (data[end - 8] & 0xFF) | ((data[end - 7] & 0xFFL) << 8) | ((data[end - 6] & 0xFFL) << 16) | ((data[end - 5] & 0xFFL) << 24);
        long isize = (data[end - 4] & 0xFF) | ((data[end - 3] & 0xFFL) << 8) | ((data[end - 2] & 0xFFL) << 16) | ((data[end - 1] & 0xFFL) << 24);
        gzipProps.put("CRC32", crc32);
        gzipProps.put("ISIZE", isize);
    }

    public void printProperties() {
        System.out.println("GZIP Properties:");
        gzipProps.forEach((key, value) -> System.out.println("- " + key + ": " + value));
        System.out.println("\nTAR Entry Properties:");
        for (Map<String, Object> entry : tarEntries) {
            System.out.println("Entry: " + entry.get("Name"));
            entry.forEach((key, value) -> {
                if (!key.equals("Name")) System.out.println("  - " + key + ": " + value);
            });
        }
    }

    public void write(String outputFilename, List<String> filesToArchive) throws IOException {
        ByteArrayOutputStream tarBaos = new ByteArrayOutputStream();
        for (String filepath : filesToArchive) {
            byte[] content = Files.readAllBytes(Paths.get(filepath));
            PosixFileAttributes attrs = Files.readAttributes(Paths.get(filepath), PosixFileAttributes.class);
            String name = Paths.get(filepath).getFileName().toString();
            String mode = Integer.toOctalString(attrs.permissions().stream().mapToInt(p -> p.ordinal()).reduce(0, (a, b) -> a | (1 << b)));
            String uid = Integer.toOctalString(attrs.owner().getUid());
            String gid = Integer.toOctalString(attrs.group().getGid());
            String size = Long.toOctalString(content.length);
            String mtime = Long.toOctalString(attrs.lastModifiedTime().toInstant().getEpochSecond());
            char typeflag = '0'; // Regular file
            String linkname = "";
            String magic = "ustar";
            String version = "00";
            String uname = attrs.owner().getName();
            String gname = attrs.group().getName();
            String devmajor = "0000000";
            String devminor = "0000000";
            String prefix = "";
            String headerStr = name + new String(new char[100 - name.length()]).replace('\0', ' ') +
                    mode + '\0' + uid + '\0' + gid + '\0' + size + '\0' + mtime + '\0' +
                    "        " + typeflag + linkname + new String(new char[100 - linkname.length()]).replace('\0', ' ') +
                    magic + '\0' + version + uname + new String(new char[32 - uname.length()]).replace('\0', ' ') +
                    gname + new String(new char[32 - gname.length()]).replace('\0', ' ') + devmajor + '\0' + devminor + '\0' +
                    prefix + new String(new char[155 - prefix.length()]).replace('\0', ' ') + new String(new char[12]).replace('\0', ' ');
            byte[] header = headerStr.getBytes();
            // Checksum
            int chksum = 0;
            for (byte b : header) chksum += b & 0xFF;
            String chksumStr = Integer.toOctalString(chksum);
            System.arraycopy((chksumStr + new String(new char[8 - chksumStr.length()]).replace('\0', ' ')).getBytes(), 0, header, 148, 8);
            tarBaos.write(header);
            tarBaos.write(content);
            int pad = (512 - (content.length % 512)) % 512;
            tarBaos.write(new byte[pad]);
        }
        tarBaos.write(new byte[1024]); // Two zero blocks
        // GZIP
        try (FileOutputStream fos = new FileOutputStream(outputFilename);
             GZIPOutputStream gos = new GZIPOutputStream(fos)) {
            gos.write(tarBaos.toByteArray());
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     TGZHandler handler = new TGZHandler("example.tgz");
    //     handler.read();
    //     handler.printProperties();
    //     handler.write("new.tgz", Arrays.asList("file1.txt", "file2.txt"));
    // }
}
  1. JavaScript Class for .TGZ Handling

The following JavaScript class is for Node.js (requires fs, zlib modules). It parses, decompresses, and prints properties.

const fs = require('fs');
const zlib = require('zlib');

class TGZHandler {
  constructor(filename) {
    this.filename = filename;
    this.gzipProps = {};
    this.tarEntries = [];
  }

  read() {
    const data = fs.readFileSync(this.filename);
    let pos = 0;
    this.gzipProps.ID1 = data[pos++];
    this.gzipProps.ID2 = data[pos++];
    this.gzipProps.CM = data[pos++];
    this.gzipProps.FLG = data[pos++];
    this.gzipProps.MTIME = data.readUInt32LE(pos); pos += 4;
    this.gzipProps.XFL = data[pos++];
    this.gzipProps.OS = data[pos++];

    if (this.gzipProps.FLG & 4) { // FEXTRA
      const xlen = data.readUInt16LE(pos); pos += 2;
      this.gzipProps['Extra Length'] = xlen;
      pos += xlen;
    }
    if (this.gzipProps.FLG & 8) { // FNAME
      let start = pos;
      while (data[pos] !== 0) pos++;
      this.gzipProps['Original Filename'] = data.slice(start, pos).toString();
      pos++;
    }
    if (this.gzipProps.FLG & 16) { // FCOMMENT
      let start = pos;
      while (data[pos] !== 0) pos++;
      this.gzipProps.Comment = data.slice(start, pos).toString();
      pos++;
    }
    if (this.gzipProps.FLG & 2) { // FHCRC
      this.gzipProps['Header CRC'] = data.readUInt16LE(pos); pos += 2;
    }
    const compressed = data.slice(pos, -8);
    const decompressed = zlib.gunzipSync(compressed);
    let tarPos = 0;
    while (tarPos < decompressed.length) {
      const header = decompressed.slice(tarPos, tarPos + 512);
      if (header.every(b => b === 0)) break;
      const name = header.slice(0, 100).toString().replace(/\0.*$/, '');
      if (!name) break;
      const mode = parseInt(header.slice(100, 108).toString().replace(/\0.*$/, ''), 8);
      const uid = parseInt(header.slice(108, 116).toString().replace(/\0.*$/, ''), 8);
      const gid = parseInt(header.slice(116, 124).toString().replace(/\0.*$/, ''), 8);
      const size = parseInt(header.slice(124, 136).toString().replace(/\0.*$/, ''), 8);
      const mtime = parseInt(header.slice(136, 148).toString().replace(/\0.*$/, ''), 8);
      const chksum = parseInt(header.slice(148, 156).toString().replace(/\0.*$/, ''), 8);
      const typeflag = String.fromCharCode(header[156]);
      const linkname = header.slice(157, 257).toString().replace(/\0.*$/, '');
      const magic = header.slice(257, 263).toString().replace(/\0.*$/, '');
      const version = header.slice(263, 265).toString().replace(/\0.*$/, '');
      const uname = header.slice(265, 297).toString().replace(/\0.*$/, '');
      const gname = header.slice(297, 329).toString().replace(/\0.*$/, '');
      const devmajor = parseInt(header.slice(329, 337).toString().replace(/\0.*$/, ''), 8);
      const devminor = parseInt(header.slice(337, 345).toString().replace(/\0.*$/, ''), 8);
      const prefix = header.slice(345, 500).toString().replace(/\0.*$/, '');
      this.tarEntries.push({
        Name: prefix ? `${prefix}/${name}` : name,
        Mode: mode.toString(8),
        UID: uid,
        GID: gid,
        Size: size,
        MTime: mtime,
        Checksum: chksum,
        Type: typeflag,
        Linkname: linkname,
        Magic: magic,
        Version: version,
        UName: uname,
        GName: gname,
        DevMajor: devmajor,
        DevMinor: devminor
      });
      tarPos += 512 + Math.ceil(size / 512) * 512;
    }
    this.gzipProps.CRC32 = data.readUInt32LE(data.length - 8);
    this.gzipProps.ISIZE = data.readUInt32LE(data.length - 4);
  }

  printProperties() {
    console.log('GZIP Properties:');
    for (const [key, value] of Object.entries(this.gzipProps)) {
      console.log(`- ${key}: ${value}`);
    }
    console.log('\nTAR Entry Properties:');
    for (const entry of this.tarEntries) {
      console.log(`Entry: ${entry.Name}`);
      for (const [key, value] of Object.entries(entry)) {
        if (key !== 'Name') console.log(`  - ${key}: ${value}`);
      }
    }
  }

  write(outputFilename, filesToArchive) {
    let tarData = Buffer.alloc(0);
    filesToArchive.forEach(filepath => {
      const content = fs.readFileSync(filepath);
      const stat = fs.statSync(filepath);
      const name = filepath.split('/').pop().slice(0, 100);
      const mode = '0' + (stat.mode & 0o777).toString(8);
      const uid = stat.uid.toString(8);
      const gid = stat.gid.toString(8);
      const size = content.length.toString(8);
      const mtime = Math.floor(stat.mtimeMs / 1000).toString(8);
      const typeflag = '0';
      const linkname = '';
      const magic = 'ustar\0';
      const version = '00';
      const uname = process.getuid ? process.getuid().toString() : 'user'; // Approximate
      const gname = process.getgid ? process.getgid().toString() : 'group';
      const devmajor = '0000000\0';
      const devminor = '0000000\0';
      const prefix = '';
      let header = Buffer.alloc(512);
      header.write(name, 0, name.length);
      header.write(mode.padStart(7, '0') + '\0', 100);
      header.write(uid.padStart(7, '0') + '\0', 108);
      header.write(gid.padStart(7, '0') + '\0', 116);
      header.write(size.padStart(11, '0') + '\0', 124);
      header.write(mtime.padStart(11, '0') + '\0', 136);
      header.write('        ', 148); // Placeholder for chksum
      header[156] = typeflag.charCodeAt(0);
      header.write(linkname, 157, linkname.length);
      header.write(magic, 257);
      header.write(version, 263);
      header.write(uname, 265, uname.length);
      header.write(gname, 297, gname.length);
      header.write(devmajor, 329);
      header.write(devminor, 337);
      header.write(prefix, 345, prefix.length);
      // Checksum
      let chksum = 0;
      for (let i = 0; i < 512; i++) chksum += header[i];
      header.write(chksum.toString(8).padStart(6, '0') + '\0 ', 148);
      tarData = Buffer.concat([tarData, header, content]);
      const pad = (512 - (content.length % 512)) % 512;
      tarData = Buffer.concat([tarData, Buffer.alloc(pad)]);
    });
    tarData = Buffer.concat([tarData, Buffer.alloc(1024)]); // Zero blocks
    const compressed = zlib.gzipSync(tarData);
    fs.writeFileSync(outputFilename, compressed);
  }
}

// Example usage:
// const handler = new TGZHandler('example.tgz');
// handler.read();
// handler.printProperties();
// handler.write('new.tgz', ['file1.txt', 'file2.txt']);
  1. C Class for .TGZ Handling

The following C code defines a struct-based "class" (using functions) for handling .TGZ. It uses zlib for decompression (assume linked with -lz). Parsing is manual.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <zlib.h>
#include <fcntl.h>
#include <unistd.h>

typedef struct {
    char *filename;
    struct {
        unsigned char id1, id2, cm, flg, xfl, os;
        unsigned int mtime, crc32, isize;
        unsigned short extra_len, header_crc;
        char *fname, *comment, *extra;
    } gzip_props;
    struct TarEntry {
        char *name;
        unsigned int mode, uid, gid, size, mtime, chksum, devmajor, devminor;
        char typeflag;
        char *linkname, *magic, *version, *uname, *gname, *prefix;
        struct TarEntry *next;
    } *tar_entries;
} TGZHandler;

void init_tgz_handler(TGZHandler *handler, const char *filename) {
    handler->filename = strdup(filename);
    memset(&handler->gzip_props, 0, sizeof(handler->gzip_props));
    handler->tar_entries = NULL;
}

void free_tgz_handler(TGZHandler *handler) {
    free(handler->filename);
    free(handler->gzip_props.fname);
    free(handler->gzip_props.comment);
    free(handler->gzip_props.extra);
    struct TarEntry *entry = handler->tar_entries;
    while (entry) {
        struct TarEntry *next = entry->next;
        free(entry->name);
        free(entry->linkname);
        free(entry->magic);
        free(entry->version);
        free(entry->uname);
        free(entry->gname);
        free(entry->prefix);
        free(entry);
        entry = next;
    }
}

int read_file(const char *filename, unsigned char **data, size_t *size) {
    FILE *f = fopen(filename, "rb");
    if (!f) return -1;
    fseek(f, 0, SEEK_END);
    *size = ftell(f);
    fseek(f, 0, SEEK_SET);
    *data = malloc(*size);
    fread(*data, 1, *size, f);
    fclose(f);
    return 0;
}

void tgz_read(TGZHandler *handler) {
    unsigned char *data;
    size_t size;
    if (read_file(handler->filename, &data, &size) < 0) return;

    size_t pos = 0;
    handler->gzip_props.id1 = data[pos++];
    handler->gzip_props.id2 = data[pos++];
    handler->gzip_props.cm = data[pos++];
    handler->gzip_props.flg = data[pos++];
    handler->gzip_props.mtime = data[pos] | (data[pos+1] << 8) | (data[pos+2] << 16) | (data[pos+3] << 24); pos += 4;
    handler->gzip_props.xfl = data[pos++];
    handler->gzip_props.os = data[pos++];

    if (handler->gzip_props.flg & 4) {
        handler->gzip_props.extra_len = data[pos] | (data[pos+1] << 8); pos += 2;
        handler->gzip_props.extra = malloc(handler->gzip_props.extra_len);
        memcpy(handler->gzip_props.extra, data + pos, handler->gzip_props.extra_len);
        pos += handler->gzip_props.extra_len;
    }
    if (handler->gzip_props.flg & 8) {
        size_t start = pos;
        while (data[pos]) pos++;
        handler->gzip_props.fname = malloc(pos - start + 1);
        memcpy(handler->gzip_props.fname, data + start, pos - start);
        handler->gzip_props.fname[pos - start] = '\0';
        pos++;
    }
    if (handler->gzip_props.flg & 16) {
        size_t start = pos;
        while (data[pos]) pos++;
        handler->gzip_props.comment = malloc(pos - start + 1);
        memcpy(handler->gzip_props.comment, data + start, pos - start);
        handler->gzip_props.comment[pos - start] = '\0';
        pos++;
    }
    if (handler->gzip_props.flg & 2) {
        handler->gzip_props.header_crc = data[pos] | (data[pos+1] << 8); pos += 2;
    }

    z_stream stream;
    stream.zalloc = Z_NULL;
    stream.zfree = Z_NULL;
    stream.opaque = Z_NULL;
    inflateInit2(&stream, -MAX_WBITS); // Raw deflate
    stream.avail_in = size - pos - 8;
    stream.next_in = data + pos;
    unsigned char *decompressed = malloc(1024 * 1024 * 10); // Assume max size
    stream.avail_out = 1024 * 1024 * 10;
    stream.next_out = decompressed;
    inflate(&stream, Z_FINISH);
    size_t dec_size = stream.total_out;
    inflateEnd(&stream);

    size_t tar_pos = 0;
    struct TarEntry *last = NULL;
    while (tar_pos < dec_size) {
        unsigned char *header = decompressed + tar_pos;
        int all_zero = 1;
        for (int i = 0; i < 512; i++) if (header[i]) { all_zero = 0; break; }
        if (all_zero) break;
        struct TarEntry *entry = malloc(sizeof(struct TarEntry));
        memset(entry, 0, sizeof(*entry));
        entry->name = strndup((char*)header, 100);
        entry->mode = strtol((char*)header + 100, NULL, 8);
        entry->uid = strtol((char*)header + 108, NULL, 8);
        entry->gid = strtol((char*)header + 116, NULL, 8);
        entry->size = strtol((char*)header + 124, NULL, 8);
        entry->mtime = strtol((char*)header + 136, NULL, 8);
        entry->chksum = strtol((char*)header + 148, NULL, 8);
        entry->typeflag = header[156];
        entry->linkname = strndup((char*)header + 157, 100);
        entry->magic = strndup((char*)header + 257, 6);
        entry->version = strndup((char*)header + 263, 2);
        entry->uname = strndup((char*)header + 265, 32);
        entry->gname = strndup((char*)header + 297, 32);
        entry->devmajor = strtol((char*)header + 329, NULL, 8);
        entry->devminor = strtol((char*)header + 337, NULL, 8);
        entry->prefix = strndup((char*)header + 345, 155);
        if (!handler->tar_entries) handler->tar_entries = entry;
        if (last) last->next = entry;
        last = entry;
        tar_pos += 512 + ((entry->size + 511) / 512 * 512);
    }

    handler->gzip_props.crc32 = data[size - 8] | (data[size - 7] << 8) | (data[size - 6] << 16) | (data[size - 5] << 24);
    handler->gzip_props.isize = data[size - 4] | (data[size - 3] << 8) | (data[size - 2] << 16) | (data[size - 1] << 24);

    free(decompressed);
    free(data);
}

void tgz_print_properties(const TGZHandler *handler) {
    printf("GZIP Properties:\n");
    printf("- ID1: %u\n", handler->gzip_props.id1);
    printf("- ID2: %u\n", handler->gzip_props.id2);
    printf("- CM: %u\n", handler->gzip_props.cm);
    printf("- FLG: %u\n", handler->gzip_props.flg);
    printf("- MTIME: %u\n", handler->gzip_props.mtime);
    printf("- XFL: %u\n", handler->gzip_props.xfl);
    printf("- OS: %u\n", handler->gzip_props.os);
    if (handler->gzip_props.extra_len) printf("- Extra Length: %u\n", handler->gzip_props.extra_len);
    if (handler->gzip_props.fname) printf("- Original Filename: %s\n", handler->gzip_props.fname);
    if (handler->gzip_props.comment) printf("- Comment: %s\n", handler->gzip_props.comment);
    if (handler->gzip_props.header_crc) printf("- Header CRC: %u\n", handler->gzip_props.header_crc);
    printf("- CRC32: %u\n", handler->gzip_props.crc32);
    printf("- ISIZE: %u\n", handler->gzip_props.isize);

    printf("\nTAR Entry Properties:\n");
    struct TarEntry *entry = handler->tar_entries;
    while (entry) {
        printf("Entry: %s%s%s\n", entry->prefix ? entry->prefix : "", entry->prefix ? "/" : "", entry->name);
        printf("  - Mode: %o\n", entry->mode);
        printf("  - UID: %u\n", entry->uid);
        printf("  - GID: %u\n", entry->gid);
        printf("  - Size: %u\n", entry->size);
        printf("  - MTime: %u\n", entry->mtime);
        printf("  - Checksum: %u\n", entry->chksum);
        printf("  - Type: %c\n", entry->typeflag);
        printf("  - Linkname: %s\n", entry->linkname);
        printf("  - Magic: %s\n", entry->magic);
        printf("  - Version: %s\n", entry->version);
        printf("  - UName: %s\n", entry->uname);
        printf("  - GName: %s\n", entry->gname);
        printf("  - DevMajor: %u\n", entry->devmajor);
        printf("  - DevMinor: %u\n", entry->devminor);
        entry = entry->next;
    }
}

void tgz_write(const char *output_filename, const char **files_to_archive, int num_files) {
    FILE *tar_f = tmpfile();
    for (int i = 0; i < num_files; i++) {
        const char *filepath = files_to_archive[i];
        FILE *fin = fopen(filepath, "rb");
        if (!fin) continue;
        fseek(fin, 0, SEEK_END);
        size_t content_size = ftell(fin);
        fseek(fin, 0, SEEK_SET);
        unsigned char *content = malloc(content_size);
        fread(content, 1, content_size, fin);
        fclose(fin);

        struct stat st;
        stat(filepath, &st);
        char name[101]; strncpy(name, strrchr(filepath, '/') ? strrchr(filepath, '/') + 1 : filepath, 100); name[100] = '\0';
        char mode[8]; snprintf(mode, 8, "%07o", st.st_mode & 0777);
        char uid[8]; snprintf(uid, 8, "%07o", st.st_uid);
        char gid[8]; snprintf(gid, 8, "%07o", st.st_gid);
        char size[12]; snprintf(size, 12, "%011o", (unsigned)content_size);
        char mtime[12]; snprintf(mtime, 12, "%011o", (unsigned)st.st_mtime);
        char typeflag = '0';
        char linkname[101] = {0};
        char magic[7] = "ustar\0";
        char version[3] = "00";
        char uname[33]; getlogin_r(uname, 32); uname[32] = '\0';
        char gname[33]; getlogin_r(gname, 32); gname[32] = '\0';
        char devmajor[8] = "0000000\0";
        char devminor[8] = "0000000\0";
        char prefix[156] = {0};
        char header[512] = {0};
        memcpy(header, name, strlen(name));
        memcpy(header + 100, mode, 7); header[107] = '\0';
        memcpy(header + 108, uid, 7); header[115] = '\0';
        memcpy(header + 116, gid, 7); header[123] = '\0';
        memcpy(header + 124, size, 11); header[135] = '\0';
        memcpy(header + 136, mtime, 11); header[147] = '\0';
        memset(header + 148, ' ', 8);
        header[156] = typeflag;
        memcpy(header + 157, linkname, strlen(linkname));
        memcpy(header + 257, magic, 6);
        memcpy(header + 263, version, 2);
        memcpy(header + 265, uname, strlen(uname));
        memcpy(header + 297, gname, strlen(gname));
        memcpy(header + 329, devmajor, 8);
        memcpy(header + 337, devminor, 8);
        memcpy(header + 345, prefix, strlen(prefix));
        // Checksum
        unsigned chksum = 0;
        for (int j = 0; j < 512; j++) chksum += header[j];
        char chksum_str[9];
        snprintf(chksum_str, 9, "%06o\0 ", chksum);
        memcpy(header + 148, chksum_str, 8);
        fwrite(header, 1, 512, tar_f);
        fwrite(content, 1, content_size, tar_f);
        size_t pad = (512 - (content_size % 512)) % 512;
        char zero_pad[512] = {0};
        fwrite(zero_pad, 1, pad, tar_f);
        free(content);
    }
    char zero_block[1024] = {0};
    fwrite(zero_block, 1, 1024, tar_f);

    fseek(tar_f, 0, SEEK_END);
    size_t tar_size = ftell(tar_f);
    fseek(tar_f, 0, SEEK_SET);
    unsigned char *tar_data = malloc(tar_size);
    fread(tar_data, 1, tar_size, tar_f);
    fclose(tar_f);

    uLongf compressed_size = compressBound(tar_size);
    unsigned char *compressed = malloc(compressed_size);
    compress(compressed, &compressed_size, tar_data, tar_size);

    FILE *out = fopen(output_filename, "wb");
    // Write GZIP header (simple, no optionals)
    unsigned char gzip_header[10] = {0x1F, 0x8B, 8, 0, 0, 0, 0, 0, 0, 3}; // OS Unix
    time_t now = time(NULL);
    memcpy(gzip_header + 4, &now, 4);
    fwrite(gzip_header, 1, 10, out);
    fwrite(compressed, 1, compressed_size, out);
    uLong crc = crc32(0L, tar_data, tar_size);
    fwrite(&crc, 4, 1, out);
    uLong isize = tar_size;
    fwrite(&isize, 4, 1, out);
    fclose(out);

    free(tar_data);
    free(compressed);
}

// Example usage:
// int main() {
//     TGZHandler handler;
//     init_tgz_handler(&handler, "example.tgz");
//     tgz_read(&handler);
//     tgz_print_properties(&handler);
//     const char *files[] = {"file1.txt", "file2.txt"};
//     tgz_write("new.tgz", files, 2);
//     free_tgz_handler(&handler);
//     return 0;
// }