#!/usr/bin/env python3
#
# This script is in the public domain
#
# This script accepts a tarball on standard input and prints a tarball on
# standard output with the same contents but all uid and gid ownership
# information shifted by the value given as first command line argument.
#
# A tool like this should be written in C but libarchive has issues:
# https://github.com/libarchive/libarchive/issues/587
# https://github.com/libarchive/libarchive/pull/1288/ (needs 3.4.1)
# Should these issues get fixed, then a good template is tarfilter.c in the
# examples directory of libarchive.
#
# We are not using Perl either, because Archive::Tar slurps the whole tarball
# into memory.
#
# We could also use Go but meh...
# https://stackoverflow.com/a/59542307/784669

import tarfile
import sys
import argparse


def main():
    parser = argparse.ArgumentParser(
        description="""\
Accepts a tarball on standard input and prints a tarball on standard output
with the same contents but all uid and gid ownership information shifted by the
value given as first command line argument.
"""
    )
    parser.add_argument(
        "idshift",
        metavar="NUM",
        type=int,
        help="Integer value by which to shift the uid and gid of each entry",
    )
    args = parser.parse_args()

    # starting with Python 3.8, the default format became PAX_FORMAT, so this
    # is only for compatibility with older versions of Python 3
    with tarfile.open(fileobj=sys.stdin.buffer, mode="r|*") as in_tar, tarfile.open(
        fileobj=sys.stdout.buffer, mode="w|", format=tarfile.PAX_FORMAT
    ) as out_tar:
        for member in in_tar:
            if args.idshift < 0 and -args.idshift > member.uid:
                print("uid cannot be negative", file=sys.stderr)
                exit(1)
            if args.idshift < 0 and -args.idshift > member.gid:
                print("gid cannot be negative", file=sys.stderr)
                exit(1)

            member.uid += args.idshift
            member.gid += args.idshift
            if member.isfile():
                with in_tar.extractfile(member) as file:
                    out_tar.addfile(member, file)
            else:
                out_tar.addfile(member)


if __name__ == "__main__":
    main()