# -*- mode: python -*-
# cpiowriter: A barebones initramfs writer
# Copyright © 2014 Andy Lutomirski
# Licensed under the GPLv2, which is available in the virtme distribution
# as a file called LICENSE with SHA-256 hash:
# 8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643

import sys

class CpioWriter(object):
    TYPE_DIR =     0o0040000
    TYPE_REG =     0o0100000
    TYPE_SYMLINK = 0o0120000
    TYPE_CHRDEV =  0o0020000
    TYPE_MASK =    0o0170000

    def __init__(self, f):
        self.__f = f
        self.__totalsize = 0
        self.__next_ino = 0

    def __write(self, data):
        self.__f.write(data)
        self.__totalsize += len(data)

    def write_object(self, name, body, mode, ino=None, nlink=None,
                     uid=0, gid=0, mtime=0, devmajor=0, devminor=0,
                     rdevmajor=0, rdevminor=0):
        if nlink is None:
            nlink = (2 if (mode & CpioWriter.TYPE_MASK) == CpioWriter.TYPE_DIR
                     else 1)

        if b'\0' in name:
            raise ValueError('Filename cannot contain a NUL')

        namesize = len(name) + 1

        if isinstance(body, bytes):
            filesize = len(body)
        else:
            filesize = body.seek(0, 2)
            body.seek(0)

        if ino is None:
            ino = self.__next_ino
            self.__next_ino += 1

        fields = [ino, mode, uid, gid, nlink, mtime, filesize,
                  devmajor, devminor, rdevmajor, rdevminor, namesize, 0]
        hdr = ('070701' + ''.join('%08X' % f for f in fields)).encode('ascii')

        self.__write(hdr)
        self.__write(name)
        self.__write(b'\0')
        self.__write(((2-namesize) % 4) * b'\0')

        if isinstance(body, bytes):
            self.__write(body)
        else:
            while True:
                buf = body.read(65536)
                if buf == b'':
                    break
                self.__write(buf)

        self.__write(((-filesize) % 4) * b'\0')

    def write_trailer(self):
        self.write_object(name=b'TRAILER!!!', body=b'', mode=0, ino=0, nlink=1)
        self.__write(((-self.__totalsize) % 512) * b'\0')

    def mkdir(self, name, mode):
        self.write_object(name=name, mode=CpioWriter.TYPE_DIR | mode, body=b'')

    def symlink(self, src, dst):
        self.write_object(name=dst, mode=CpioWriter.TYPE_SYMLINK | 0o777,
                          body=src)

    def write_file(self, name, body, mode):
        self.write_object(name=name, body=body, mode=CpioWriter.TYPE_REG | mode)

    def mkchardev(self, name, dev, mode):
        major,minor = dev
        self.write_object(name=name, mode=CpioWriter.TYPE_CHRDEV | mode,
                          rdevmajor=major, rdevminor=minor,
                          body=b'')
