/******************************* LICENSE **************************************
* Any code in this file may be redistributed or modified under the terms of
* the GNU General Public License as published by the Free Software
* Foundation; version 2 of the license.
****************************** END LICENSE ***********************************/

/******************************************************************************
* Author:
* Andrew Smith, http://littlesvr.ca/misc/contactandrew.php
*
* Copyright 2005-2007 Andrew Smith <andrew-smith@mail.ru>
*
* Contributors:
*
******************************************************************************/

/******************************************************************************
* Functions in this file write to volInfo.imageForWriting and are probably
* unsutable for anything else.
******************************************************************************/

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

#include "bk.h"
#include "bkInternal.h"
#include "bkWrite7x.h"
#include "bkTime.h"
#include "bkWrite.h"
#include "bkMangle.h"
#include "bkError.h"
#include "bkSort.h"
#include "bkPath.h"
#include "bkCache.h"
#include "bkRead7x.h"
#include "bkLink.h"

/******************************************************************************
* bk_write_image()
* Writes everything from first to last byte of the iso.
* Public function.
* */
int bk_write_image(const char* newImagePathAndName, VolInfo* volInfo,
                   time_t creationTime, int filenameTypes,
                   void(*progressFunction)(VolInfo*, double))
{
    int rc;
    struct stat statStruct;
    DirToWrite newTree;
    off_t svdOffset;
    off_t pRealRootDrOffset;
    int pRootDirSize;
    off_t sRealRootDrOffset;
    int sRootDirSize;
    off_t lPathTable9660Loc;
    off_t mPathTable9660Loc;
    int pathTable9660Size;
    off_t lPathTableJolietLoc;
    off_t mPathTableJolietLoc;
    int pathTableJolietSize;
    off_t bootCatalogSectorNumberOffset;
    off_t currPos;

    volInfo->writeProgressFunction = progressFunction;
    volInfo->stopOperation = false;

    volInfo->estimatedIsoSize = bk_estimate_iso_size(volInfo, filenameTypes);
    progressFunction(volInfo, 0);

    rc = stat(newImagePathAndName, &statStruct);
    if (rc == 0 && statStruct.st_ino == volInfo->imageForReadingInode)
        return BKERROR_SAVE_OVERWRITE;

    /* because mangleDir works on dir's children i need to
    * copy the root manually */
    memset(&newTree, 0, sizeof(DirToWrite));
    newTree.base.posixFileMode = volInfo->dirTree.base.posixFileMode;

    printf("mangling\n"); fflush(NULL);
    /* create tree to write */
    rc = mangleDir(&(volInfo->dirTree), &newTree, filenameTypes);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        return rc;
    }

    printf("opening '%s' for writing\n", newImagePathAndName); fflush(NULL);
    volInfo->imageForWriting = open(newImagePathAndName,
                                    O_WRONLY | O_CREAT | O_TRUNC,
                                    S_IRUSR | S_IWUSR);
    if (volInfo->imageForWriting == -1) {
        freeDirToWriteContents(&newTree);
        return BKERROR_OPEN_WRITE_FAILED;
    }

    printf("writing blank at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
    /* system area, always zeroes */
    rc = writeByteBlock(volInfo, 0, NBYTES_LOGICAL_BLOCK * NLS_SYSTEM_AREA);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        close(volInfo->imageForWriting);
        unlink(newImagePathAndName);
        return rc;
    }

    /* skip pvd (1 block), write it after files */
    wcSeekForward(volInfo, NBYTES_LOGICAL_BLOCK);

    if (volInfo->bootMediaType != BOOT_MEDIA_NONE) {
        /* el torito volume descriptor */
        rc = writeElToritoVd(volInfo, &bootCatalogSectorNumberOffset);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
    }

    if (filenameTypes & FNTYPE_JOLIET)
        /* skip svd (1 block), write it after pvd */
    {
        svdOffset = wcSeekTell(volInfo);
        wcSeekForward(volInfo, NBYTES_LOGICAL_BLOCK);
    }

    printf("writing terminator at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
    /* volume descriptor set terminator */
    rc = writeVdsetTerminator(volInfo);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        close(volInfo->imageForWriting);
        unlink(newImagePathAndName);
        return rc;
    }

    if (volInfo->bootMediaType != BOOT_MEDIA_NONE) {
        /* write boot catalog sector number */
        currPos = wcSeekTell(volInfo);
        wcSeekSet(volInfo, bootCatalogSectorNumberOffset);
        rc = write731(volInfo, currPos / NBYTES_LOGICAL_BLOCK);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
        wcSeekSet(volInfo, currPos);

        /* write el torito booting catalog */
        rc = writeElToritoBootCatalog(volInfo, &(volInfo->bootRecordSectorNumberOffset));
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
    }

    /* MAYBE write boot record file now */
    if (volInfo->bootMediaType != BOOT_MEDIA_NONE &&
            !volInfo->bootRecordIsVisible) {
        int blankSize;
        int srcFile; /* either the old image or the boot record file on
                     * the regular filesystem */
        bool srcFileOpened;

        /* set up source file pointer */
        if (volInfo->bootRecordIsOnImage) {
            srcFile = volInfo->imageForReading;
            lseek(volInfo->imageForReading, volInfo->bootRecordOffset, SEEK_SET);
            srcFileOpened = false;
        } else {
            srcFile = open(volInfo->bootRecordPathAndName, O_RDONLY);
            if (srcFile == -1) {
                freeDirToWriteContents(&newTree);
                close(volInfo->imageForWriting);
                unlink(newImagePathAndName);
                return BKERROR_OPEN_READ_FAILED;
            }
            srcFileOpened = true;
        }

        /* write boot record sector number */
        currPos = wcSeekTell(volInfo);
        wcSeekSet(volInfo, volInfo->bootRecordSectorNumberOffset);

        rc = write731(volInfo, currPos / NBYTES_LOGICAL_BLOCK);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            if (srcFileOpened)
                close(srcFile);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
        wcSeekSet(volInfo, currPos);

        /* file contents */
        rc = writeByteBlockFromFile(srcFile, volInfo, volInfo->bootRecordSize);
        if (rc < 0) {
            freeDirToWriteContents(&newTree);
            if (srcFileOpened)
                close(srcFile);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }

        blankSize = NBYTES_LOGICAL_BLOCK -
                    volInfo->bootRecordSize % NBYTES_LOGICAL_BLOCK;

        /* fill the last sector with 0s */
        rc = writeByteBlock(volInfo, 0x00, blankSize);
        if (rc < 0) {
            freeDirToWriteContents(&newTree);
            if (srcFileOpened)
                close(srcFile);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }

        if (srcFileOpened)
            close(srcFile);
    }
    /* END MAYBE write boot record file now */

    printf("sorting 9660\n");
    sortDir(&newTree, FNTYPE_9660);

    pRealRootDrOffset = wcSeekTell(volInfo);

    printf("writing primary directory tree at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
    /* 9660 and maybe rockridge dir tree */
    rc = writeDir(volInfo, &newTree, 0, 0, 0, creationTime,
                  filenameTypes & (FNTYPE_9660 | FNTYPE_ROCKRIDGE), true);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        close(volInfo->imageForWriting);
        unlink(newImagePathAndName);
        return rc;
    }

    pRootDirSize = rc;

    /* joliet dir tree */
    if (filenameTypes & FNTYPE_JOLIET) {
        printf("sorting joliet\n");
        sortDir(&newTree, FNTYPE_JOLIET);

        printf("writing supplementary directory tree at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
        sRealRootDrOffset = wcSeekTell(volInfo);

        rc = writeDir(volInfo, &newTree, 0, 0, 0, creationTime,
                      FNTYPE_JOLIET, true);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }

        sRootDirSize = rc;
    }

    printf("writing 9660 path tables at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);

    lPathTable9660Loc = wcSeekTell(volInfo);
    rc = writePathTable(volInfo, &newTree, true, FNTYPE_9660);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        close(volInfo->imageForWriting);
        unlink(newImagePathAndName);
        return rc;
    }
    pathTable9660Size = rc;

    mPathTable9660Loc = wcSeekTell(volInfo);
    rc = writePathTable(volInfo, &newTree, false, FNTYPE_9660);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        close(volInfo->imageForWriting);
        unlink(newImagePathAndName);
        return rc;
    }

    if (filenameTypes & FNTYPE_JOLIET) {
        printf("writing joliet path tables at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
        lPathTableJolietLoc = wcSeekTell(volInfo);
        rc = writePathTable(volInfo, &newTree, true, FNTYPE_JOLIET);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
        pathTableJolietSize = rc;

        mPathTableJolietLoc = wcSeekTell(volInfo);
        rc = writePathTable(volInfo, &newTree, false, FNTYPE_JOLIET);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
    }

    printf("writing files at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
    resetWriteStatus(volInfo->fileLocations);
    /* all files and offsets/sizes */
    rc = writeFileContents(volInfo, &newTree, filenameTypes);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        close(volInfo->imageForWriting);
        unlink(newImagePathAndName);
        return rc;
    }

    if (filenameTypes & FNTYPE_ROCKRIDGE) {
        printf("writing long NMs at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
        rc = writeLongNMsInDir(volInfo, &newTree);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
    }

    wcSeekSet(volInfo, NBYTES_LOGICAL_BLOCK * NLS_SYSTEM_AREA);

    printf("writing pvd at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
    rc = writeVolDescriptor(volInfo, pRealRootDrOffset,
                            pRootDirSize, lPathTable9660Loc, mPathTable9660Loc,
                            pathTable9660Size, creationTime, true);
    if (rc <= 0) {
        freeDirToWriteContents(&newTree);
        close(volInfo->imageForWriting);
        unlink(newImagePathAndName);
        return rc;
    }

    if (filenameTypes & FNTYPE_JOLIET) {
        wcSeekSet(volInfo, svdOffset);

        printf("writing svd at %X\n", (int)wcSeekTell(volInfo)); fflush(NULL);
        rc = writeVolDescriptor(volInfo, sRealRootDrOffset,
                                sRootDirSize, lPathTableJolietLoc, mPathTableJolietLoc,
                                pathTableJolietSize, creationTime, false);
        if (rc <= 0) {
            freeDirToWriteContents(&newTree);
            close(volInfo->imageForWriting);
            unlink(newImagePathAndName);
            return rc;
        }
    }

    printf("freeing memory\n"); fflush(NULL);
    freeDirToWriteContents(&newTree);
    close(volInfo->imageForWriting);

    return 1;
}

/******************************************************************************
* bootInfoTableChecksum()
* Calculate the checksum to be written into the boot info table.
* */
int bootInfoTableChecksum(int oldImage, FileToWrite* file, unsigned* checksum)
{
    ssize_t rc;
    int rc2;
    int srcFile;
    unsigned char* contents;
    unsigned count;

    if (file->size % 4 != 0)
        return BKERROR_WRITE_BOOT_FILE_4;

    contents = malloc(file->size);
    if (contents == NULL)
        return BKERROR_OUT_OF_MEMORY;

    if (file->onImage)
        /* read file from original image */
    {
        lseek(oldImage, file->offset, SEEK_SET);

        rc = read(oldImage, contents, file->size);
        if (rc == -1 || rc != (int)(file->size)) {
            free(contents);
            return BKERROR_READ_GENERIC;
        }
    } else
        /* read file from fs */
    {
        srcFile = open(file->pathAndName, O_RDONLY);
        if (srcFile == -1) {
            free(contents);
            return BKERROR_OPEN_READ_FAILED;
        }

        rc = read(srcFile, contents, file->size);
        if (rc == -1 || rc != (int)(file->size)) {
            close(srcFile);
            free(contents);
            return BKERROR_READ_GENERIC;
        }

        rc2 = close(srcFile);
        if (rc2 < 0) {
            free(contents);
            return BKERROR_EXOTIC;
        }
    }

    *checksum = 0;
    /* do 32 bit checksum starting from byte 64
    * because i check above that the file is divisible by 4 i will not be
    * reading wrong memory */
    for (count = 64; count < file->size; count += 4) {
        unsigned toAdd;

        toAdd = *(contents + count) | (*(contents + count + 1) << 8) |
                (*(contents + count + 2) << 16) | (*(contents + count + 3) << 24);

        *checksum += toAdd;
    }

    free(contents);

    return 1;
}

/******************************************************************************
* countDirsOnLevel()
* a 'level' is described in ecma119 6.8.2
* it's needed for path tables, don't remember exactly what for
* */
int countDirsOnLevel(const DirToWrite* dir, int targetLevel, int thisLevel)
{
    BaseToWrite* child;
    int sum;

    if (targetLevel == thisLevel) {
        return 1;
    } else {
        sum = 0;

        child = dir->children;
        while (child != NULL) {
            if (IS_DIR(child->posixFileMode))
                sum += countDirsOnLevel(DIRTW_PTR(child), targetLevel, thisLevel + 1);

            child = child->next;
        }

        return sum;
    }
}

/******************************************************************************
* countTreeHeight()
* caller should set heightSoFar to 1
* */
int countTreeHeight(const DirToWrite* dir, int heightSoFar)
{
    BaseToWrite* child;
    int maxHeight;
    int thisHeight;

    maxHeight = heightSoFar;
    child = dir->children;
    while (child != NULL) {
        if (IS_DIR(child->posixFileMode)) {
            thisHeight = countTreeHeight(DIRTW_PTR(child), heightSoFar + 1);

            if (thisHeight > maxHeight)
                maxHeight = thisHeight;
        }

        child = child->next;
    }

    return maxHeight;
}

/******************************************************************************
* elToritoChecksum()
* Algorithm: the sum of all words, including the checksum must trunkate to
* a 16-bit 0x0000
* */
unsigned short elToritoChecksum(const unsigned char* record)
{
    short sum;
    int i;

    sum = 0;
    for (i = 0; i < 32; i += 2) {
        sum += *(record + i) | (*(record + i + 1) << 8);
    }

    return 0xFFFF - sum + 1;
}

/******************************************************************************
* writeByteBlock()
* Fills numBytes with byteToWrite.

* */
int writeByteBlock(VolInfo* volInfo, unsigned char byteToWrite, int numBytes)
{
    int rc;
    int count;
    int numBlocks;
    int sizeLastBlock;

    memset(volInfo->readWriteBuffer, byteToWrite, READ_WRITE_BUFFER_SIZE);

    numBlocks = numBytes / READ_WRITE_BUFFER_SIZE;
    sizeLastBlock = numBytes % READ_WRITE_BUFFER_SIZE;

    for (count = 0; count < numBlocks; count++) {
        rc = wcWrite(volInfo, volInfo->readWriteBuffer, READ_WRITE_BUFFER_SIZE);
        if (rc <= 0)
            return rc;
    }

    if (sizeLastBlock > 0) {
        rc = wcWrite(volInfo, volInfo->readWriteBuffer, sizeLastBlock);
        if (rc <= 0)
            return rc;
    }

    return 1;
}

/******************************************************************************
* writeByteBlockFromFile()
* copies numBytes from src into the image to write in blocks of 10K
* */
int writeByteBlockFromFile(int src, VolInfo* volInfo, unsigned numBytes)
{
    int rc;
    int count;
    int numBlocks;
    int sizeLastBlock;

    numBlocks = numBytes / READ_WRITE_BUFFER_SIZE;
    sizeLastBlock = numBytes % READ_WRITE_BUFFER_SIZE;

    for (count = 0; count < numBlocks; count++) {
        if (volInfo->stopOperation)
            return BKERROR_OPER_CANCELED_BY_USER;

        rc = read(src, volInfo->readWriteBuffer, READ_WRITE_BUFFER_SIZE);
        if (rc != READ_WRITE_BUFFER_SIZE)
            return BKERROR_READ_GENERIC;
        rc = wcWrite(volInfo, volInfo->readWriteBuffer, READ_WRITE_BUFFER_SIZE);
        if (rc <= 0)
            return rc;
    }

    if (sizeLastBlock > 0) {
        rc = read(src, volInfo->readWriteBuffer, sizeLastBlock);
        if (rc != sizeLastBlock)
            return BKERROR_READ_GENERIC;
        rc = wcWrite(volInfo, volInfo->readWriteBuffer, sizeLastBlock);
        if (rc <= 0)
            return rc;
    }

    return 1;
}

/******************************************************************************
* writeDir()
* Writes the contents of a directory. Also writes locations and sizes of
* directory records for directories but not for files.
* Returns data length of the dir written.
* */
int writeDir(VolInfo* volInfo, DirToWrite* dir, int parentLbNum,
             int parentNumBytes, int parentPosix, time_t recordingTime,
             int filenameTypes, bool isRoot)
{
    int rc;

    off_t startPos;
    int numUnusedBytes;
    off_t endPos;

    DirToWrite selfDir; /* will have a different filename */
    DirToWrite parentDir;

    BaseToWrite* child;

    if (wcSeekTell(volInfo) % NBYTES_LOGICAL_BLOCK != 0)
        return BKERROR_SANITY;

    /* names other than 9660 are not used for self and parent */
    selfDir.base.name9660[0] = 0x00;
    selfDir.base.posixFileMode = dir->base.posixFileMode;

    parentDir.base.name9660[0] = 0x01;
    parentDir.base.name9660[1] = '\0';
    if (isRoot)
        parentDir.base.posixFileMode = selfDir.base.posixFileMode;
    else
        parentDir.base.posixFileMode = parentPosix;

    startPos = wcSeekTell(volInfo);

    if (startPos % NBYTES_LOGICAL_BLOCK != 0)
        /* this should never happen */
        return BKERROR_SANITY;

    if (filenameTypes & FNTYPE_JOLIET)
        dir->extentNumber2 = startPos / NBYTES_LOGICAL_BLOCK;
    else
        dir->base.extentNumber = startPos / NBYTES_LOGICAL_BLOCK;

    /* write self */
    if (isRoot) {
        rc = writeDr(volInfo, BASETW_PTR(&selfDir), recordingTime, true, true, true, filenameTypes);
        if (rc < 0)
            return rc;

        if (filenameTypes & FNTYPE_JOLIET)
            dir->base.extentLocationOffset2 = selfDir.base.extentLocationOffset2;
        else
            dir->base.extentLocationOffset = selfDir.base.extentLocationOffset;
    } else {
        rc = writeDr(volInfo, BASETW_PTR(&selfDir), recordingTime, true, true, false, filenameTypes);
        if (rc < 0)
            return rc;
    }
    if (rc < 0)
        return rc;

    /* write parent */
    rc = writeDr(volInfo, BASETW_PTR(&parentDir), recordingTime, true, true, false, filenameTypes);
    if (rc < 0)
        return rc;

    child = dir->children;

    /* WRITE children drs */
    while (child != NULL) {
        if (IS_DIR(child->posixFileMode)) {
            rc = writeDr(volInfo, child, recordingTime,
                         true,  false, false, filenameTypes);
        } else {
            rc = writeDr(volInfo, child, recordingTime,
                         false,  false, false, filenameTypes);
        }
        if (rc < 0)
            return rc;

        child = child->next;
    }
    /* END WRITE children drs */

    /* write blank to conclude extent */
    numUnusedBytes = NBYTES_LOGICAL_BLOCK -
                     wcSeekTell(volInfo) % NBYTES_LOGICAL_BLOCK;
    rc = writeByteBlock(volInfo, 0x00, numUnusedBytes);
    if (rc < 0)
        return rc;

    if (filenameTypes & FNTYPE_JOLIET)
        dir->dataLength2 = wcSeekTell(volInfo) - startPos;
    else
        dir->dataLength = wcSeekTell(volInfo) - startPos;

    /* write subdirectories */
    child = dir->children;
    while (child != NULL) {
        if (IS_DIR(child->posixFileMode)) {
            if (filenameTypes & FNTYPE_JOLIET) {
                rc = writeDir(volInfo, DIRTW_PTR(child), dir->extentNumber2,
                              dir->dataLength2, BASETW_PTR(dir)->posixFileMode, recordingTime,
                              filenameTypes, false);
            } else {
                rc = writeDir(volInfo, DIRTW_PTR(child), BASETW_PTR(dir)->extentNumber,
                              dir->dataLength, BASETW_PTR(dir)->posixFileMode, recordingTime,
                              filenameTypes, false);
            }
            if (rc < 0)
                return rc;
        }

        child = child->next;
    }

    endPos = wcSeekTell(volInfo);

    /* SELF extent location and size */
    if (filenameTypes & FNTYPE_JOLIET)
        wcSeekSet(volInfo, selfDir.base.extentLocationOffset2);
    else
        wcSeekSet(volInfo, selfDir.base.extentLocationOffset);

    if (filenameTypes & FNTYPE_JOLIET) {
        rc = write733(volInfo, dir->extentNumber2);
        if (rc <= 0)
            return rc;

        rc = write733(volInfo, dir->dataLength2);
        if (rc <= 0)
            return rc;
    } else {
        rc = write733(volInfo, BASETW_PTR(dir)->extentNumber);
        if (rc <= 0)
            return rc;

        rc = write733(volInfo, dir->dataLength);
        if (rc <= 0)
            return rc;
    }
    /* END SELF extent location and size */

    /* PARENT extent location and size */
    if (filenameTypes & FNTYPE_JOLIET)
        wcSeekSet(volInfo, parentDir.base.extentLocationOffset2);
    else
        wcSeekSet(volInfo, parentDir.base.extentLocationOffset);

    if (parentLbNum == 0)
        /* root, parent is same as self */
    {
        if (filenameTypes & FNTYPE_JOLIET) {
            rc = write733(volInfo, dir->extentNumber2);
            if (rc <= 0)
                return rc;

            rc = write733(volInfo, dir->dataLength2);
            if (rc <= 0)
                return rc;
        } else {
            rc = write733(volInfo, BASETW_PTR(dir)->extentNumber);
            if (rc <= 0)
                return rc;

            rc = write733(volInfo, dir->dataLength);
            if (rc <= 0)
                return rc;
        }
    } else
        /* normal parent */
    {
        rc = write733(volInfo, parentLbNum);
        if (rc <= 0)
            return rc;

        rc = write733(volInfo, parentNumBytes);
        if (rc <= 0)
            return rc;
    }
    /* END PARENT extent location and size */

    /* ALL subdir extent locations and sizes */
    child = dir->children;
    while (child != NULL) {
        if (IS_DIR(child->posixFileMode)) {
            if (filenameTypes & FNTYPE_JOLIET) {
                wcSeekSet(volInfo, child->extentLocationOffset2);

                rc = write733(volInfo, DIRTW_PTR(child)->extentNumber2);
                if (rc <= 0)
                    return rc;

                rc = write733(volInfo, DIRTW_PTR(child)->dataLength2);
                if (rc <= 0)
                    return rc;
            } else {
                wcSeekSet(volInfo, child->extentLocationOffset);

                rc = write733(volInfo, child->extentNumber);
                if (rc <= 0)
                    return rc;

                rc = write733(volInfo, DIRTW_PTR(child)->dataLength);
                if (rc <= 0)
                    return rc;
            }
        }

        child = child->next;
    }
    /* END ALL subdir extent locations and sizes */

    wcSeekSet(volInfo, endPos);

    if (filenameTypes & FNTYPE_JOLIET)
        return dir->dataLength2;
    else
        return dir->dataLength;
}

/******************************************************************************
* writeDr()
* Writes a directory record.
* Note that it uses only the members of DirToWrite and FileToWrite that are
* the same.
* */
int writeDr(VolInfo* volInfo, BaseToWrite* node, time_t recordingTime, bool isADir,
            bool isSelfOrParent, bool isFirstRecord, int filenameTypes)
{
    int rc;
    unsigned char byte;
    char aString[256];
    unsigned short aShort;
    off_t startPos;
    off_t endPos;
    unsigned char lenFileId;
    unsigned char recordLen;

    /* look at the end of the function for an explanation */
writeDrStartLabel:

    startPos = wcSeekTell(volInfo);

    /* record length is recorded in the end */
    wcSeekForward(volInfo, 1);

    /* extended attribute record length */
    byte = 0;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    if (filenameTypes & FNTYPE_JOLIET)
        node->extentLocationOffset2 = wcSeekTell(volInfo);
    else
        node->extentLocationOffset = wcSeekTell(volInfo);

    /* location of extent not recorded in this function */
    wcSeekForward(volInfo, 8);

    /* data length not recorded in this function */
    wcSeekForward(volInfo, 8);

    /* RECORDING time and date */
    epochToShortString(recordingTime, aString);

    rc = write711(volInfo, aString[0]);
    if (rc <= 0)
        return rc;
    rc = write711(volInfo, aString[1]);
    if (rc <= 0)
        return rc;
    rc = write711(volInfo, aString[2]);
    if (rc <= 0)
        return rc;
    rc = write711(volInfo, aString[3]);
    if (rc <= 0)
        return rc;
    rc = write711(volInfo, aString[4]);
    if (rc <= 0)
        return rc;
    rc = write711(volInfo, aString[5]);
    if (rc <= 0)
        return rc;
    rc = write711(volInfo, aString[6]);
    if (rc <= 0)
        return rc;
    /* END RECORDING time and date */

    /* FILE flags  */
    if (isADir)
        /* (only directory bit on) */
        byte = 0x02;
    else
        /* nothing on */
        byte = 0x00;

    rc = wcWrite(volInfo, (char*) & byte, 1);
    if (rc <= 0)
        return rc;
    /* END FILE flags  */

    /* file unit size (always 0, non-interleaved mode) */
    byte = 0;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* interleave gap size (also always 0, non-interleaved mode) */
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* volume sequence number (always 1) */
    aShort = 1;
    rc = write723(volInfo, aShort);
    if (rc <= 0)
        return rc;

    /* LENGTH of file identifier */
    if (isSelfOrParent)
        lenFileId = 1;
    else {
        if (filenameTypes & FNTYPE_JOLIET)
            lenFileId = 2 * strlen(node->nameJoliet);
        else
            /*if(isADir) see microsoft comment below */
            lenFileId = strlen(node->name9660);
        /*else
            lenFileId = strlen(node->name9660) + 2; */
    }

    rc = write711(volInfo, lenFileId);
    if (rc <= 0)
        return rc;
    /* END LENGTH of file identifier */

    /* FILE identifier */
    if (isSelfOrParent) {
        /* that byte has 0x00 or 0x01 */
        rc = write711(volInfo, node->name9660[0]);
        if (rc <= 0)
            return rc;
    } else {
        if (filenameTypes & FNTYPE_JOLIET) {
            rc = writeJolietStringField(volInfo, node->nameJoliet,
                                        2 * strlen(node->nameJoliet));
            if (rc < 0)
                return rc;
        } else {
            /* ISO9660 requires ";1" after the filename (not directory name)
            * but the windows NT/2K boot loaders cannot find NTLDR inside
            * the I386 directory because they are looking for "NTLDR" not
            * "NTLDR;1". i guess if microsoft can do it, i can do it. filenames
            * on images written by me do not end with ";1"
            if(isADir)
            {*/
            /* the name */
            rc = wcWrite(volInfo, node->name9660, lenFileId);
            if (rc <= 0)
                return rc;
            /*}
            else
            {
                rc = writeWrapper(image, node->name9660, lenFileId - 2);
                if(rc <= 0)
                    return rc;

                rc = writeWrapper(image, ";1", 2);
                if(rc <= 0)
                    return rc;
            }*/
        }
    }
    /* END FILE identifier */

    /* padding field */
    if (lenFileId % 2 == 0) {
        byte = 0;
        rc = write711(volInfo, byte);
        if (rc <= 0)
            return rc;
    }

    if (filenameTypes & FNTYPE_ROCKRIDGE) {
        if (isFirstRecord) {
            rc = writeRockSP(volInfo);
            if (rc < 0)
                return rc;

            rc = writeRockER(volInfo);
            if (rc < 0)
                return rc;
        }

        rc = writeRockPX(volInfo, node->posixFileMode, isADir);
        if (rc < 0)
            return rc;

        if (!isSelfOrParent) {
            if (wcSeekTell(volInfo) - startPos < strlen(node->nameRock) + 5)
                /* have no room for the NM entry in this directory record */
            {
                node->offsetForCE = wcSeekTell(volInfo);
                /* leave room for CE entry */
                wcSeekForward(volInfo, 28);
            } else {
                rc = writeRockNM(volInfo, node->nameRock, strlen(node->nameRock), false);
                if (rc < 0)
                    return rc;
            }

            if (IS_SYMLINK(node->posixFileMode)) {
                rc = writeRockSL(volInfo, SYMLINKTW_PTR(node), true);
                if (rc < 0)
                    return rc;
            }
        }
    }

    /* RECORD length */
    endPos = wcSeekTell(volInfo);

    wcSeekSet(volInfo, startPos);

    recordLen = endPos - startPos;
    rc = write711(volInfo, recordLen);
    if (rc <= 0)
        return rc;

    wcSeekSet(volInfo, endPos);
    /* END RECORD length */

    /* the goto is good! really!
    * if, after writing the record we see that the record is in two logical
    * sectors (that's not allowed by iso9660) we erase the record just
    * written, write zeroes to the end of the first logical sector
    * (as required by iso9660) and restart the function, which will write
    * the same record again but at the beginning of the next logical sector
    * yeah, so don't complain :) */

    if (endPos / NBYTES_LOGICAL_BLOCK > startPos / NBYTES_LOGICAL_BLOCK)
        /* crossed a logical sector boundary while writing the record */
    {
        wcSeekSet(volInfo, startPos);

        /* overwrite a piece of the record written in this function
        * (the piece that's in the first sector) with zeroes */
        rc = writeByteBlock(volInfo, 0x00, recordLen - endPos % NBYTES_LOGICAL_BLOCK);
        if (rc < 0)
            return rc;

        goto writeDrStartLabel;
    }

    return 1;
}

/******************************************************************************
* writeElToritoBootCatalog()
* Write the el torito boot catalog (validation entry and initial/default entry).
* Returns the offset where the boot record sector number should
* be written (7.3.1).
* */
int writeElToritoBootCatalog(VolInfo* volInfo,
                             off_t* bootRecordSectorNumberOffset)
{
    unsigned char buffer[NBYTES_LOGICAL_BLOCK];
    int rc;

    memset(buffer, 0, NBYTES_LOGICAL_BLOCK);

    if (wcSeekTell(volInfo) % NBYTES_LOGICAL_BLOCK != 0)
        /* file pointer not at sector boundary */
        return BKERROR_SANITY;

    /* SETUP VALIDATION entry (first 20 bytes of boot catalog) */
    /* header, must be 1 */
    buffer[0] = 1;
    /* platform id, 0 for x86 (memset at start took care of this) */
    /* 2 bytes reserved, must be 0 (memset at start took care of this) */
    /* 24 bytes id string for manufacturer/developer of cdrom */
    strncpy((char*)&(buffer[4]), "Edited with ISO Master", 22);
    /* key byte 0x55 */
    buffer[30] = 0x55;
    /* key byte 0xAA */
    buffer[31] = 0xAA;

    /* checksum */
    write721ToByteArray(&(buffer[28]), elToritoChecksum(buffer));
    /* END SETUP VALIDATION validation entry (first 20 bytes of boot catalog) */

    /* SETUP INITIAL entry (next 20 bytes of boot catalog) */
    /* boot indicator. 0x88 = bootable */
    buffer[32] = 0x88;
    /* boot media type */
    if (volInfo->bootMediaType == BOOT_MEDIA_NO_EMULATION)
        buffer[33] = 0;
    else if (volInfo->bootMediaType == BOOT_MEDIA_1_2_FLOPPY)
        buffer[33] = 1;
    else if (volInfo->bootMediaType == BOOT_MEDIA_1_44_FLOPPY)
        buffer[33] = 2;
    else if (volInfo->bootMediaType == BOOT_MEDIA_2_88_FLOPPY)
        buffer[33] = 3;
    else if (volInfo->bootMediaType == BOOT_MEDIA_HARD_DISK)
        buffer[33] = 4;
    /* load segment leave it at 0 */
    /* system type, leave it at 0 */
    /* 1 byte unused, leave it at 0 */
    /* sector count. i have yet to see a boot record with a sector count
    * that's not 4 */
    write721ToByteArray(&(buffer[38]), 4);
    /* logical block number of boot record file. this is not known until
    * after that file is written */
    *bootRecordSectorNumberOffset = wcSeekTell(volInfo) + 40;
    /* the rest is unused, leave it at 0 */
    /* END SETUP INITIAL entry (next 20 bytes of boot catalog) */

    rc = wcWrite(volInfo, (char*)buffer, NBYTES_LOGICAL_BLOCK);
    if (rc <= 0)
        return rc;

    return 1;
}

/******************************************************************************
* writeElToritoVd()
* Write the el torito volume descriptor.
* Returns the offset where the boot catalog sector number should
* be written (7.3.1).
* */
int writeElToritoVd(VolInfo* volInfo, off_t* bootCatalogSectorNumberOffset)
{
    char buffer[NBYTES_LOGICAL_BLOCK];
    int rc;

    memset(buffer, 0, NBYTES_LOGICAL_BLOCK);

    if (wcSeekTell(volInfo) % NBYTES_LOGICAL_BLOCK != 0)
        /* file pointer not at sector boundary */
        return BKERROR_SANITY;

    /* SETUP BOOT record volume descriptor sector */
    /* boot record indicator, must be 0 (memset at start took care of this) */
    /* iso9660 identifier, must be "CD001" */
    strncpy((char*)buffer + 1, "CD001", 5);
    /* version, must be 1 */
    buffer[6] = 1;
    /* boot system identifier, must be 32 bytes "EL TORITO SPECIFICATION"
    * padded with 0x00 (memset at start took care of this) */
    strncpy(&(buffer[7]), "EL TORITO SPECIFICATION", 23);
    /* unused 32 bytes, must be 0 (memset at start took care of this) */
    /* boot catalog location, 4 byte intel format. written later. */
    *bootCatalogSectorNumberOffset = wcSeekTell(volInfo) + 71;
    /*write731ToByteArray(&(buffer[71]), bootCatalogSectorNumber);*/
    /* the rest of this sector is unused, must be set to 0 */
    /* END SETUP BOOT record volume descriptor sector */

    rc = wcWrite(volInfo, buffer, NBYTES_LOGICAL_BLOCK);
    if (rc <= 0)
        return rc;

    return 1;
}

/******************************************************************************
* writeFileContents()
* Write file contents into an extent and also write the file's location and
* size into the directory records back in the tree.
* Also write location and size for symbolic links.
* */
int writeFileContents(VolInfo* volInfo, DirToWrite* dir, int filenameTypes)
{
    int rc;

    BaseToWrite* child;
    int numUnusedBytes;
    int srcFile;
    off_t endPos;

    child = dir->children;
    while (child != NULL)
        /* each file in current directory */
    {
        if (volInfo->stopOperation)
            return BKERROR_OPER_CANCELED_BY_USER;

        if (wcSeekTell(volInfo) % NBYTES_LOGICAL_BLOCK != 0)
            return BKERROR_SANITY;

        if (IS_REG_FILE(child->posixFileMode)) {
            bool needToCopy = true;

            child->extentNumber = wcSeekTell(volInfo) / NBYTES_LOGICAL_BLOCK;
            if (volInfo->scanForDuplicateFiles) {
                if (FILETW_PTR(child)->location->extentNumberWrittenTo == 0)
                    /* file not yet written */
                {
                    FILETW_PTR(child)->location->extentNumberWrittenTo = child->extentNumber;
                } else {
                    child->extentNumber = FILETW_PTR(child)->location->extentNumberWrittenTo;
                    needToCopy = false;
                }
            }

            if (volInfo->bootMediaType != BOOT_MEDIA_NONE &&
                    volInfo->bootRecordIsVisible &&
                    FILETW_PTR(child)->origFile == volInfo->bootRecordOnImage)
                /* this file is the boot record. write its sector number in
                * the boot catalog */
            {
                off_t currPos;

                currPos = wcSeekTell(volInfo);

                wcSeekSet(volInfo, volInfo->bootRecordSectorNumberOffset);
                rc = write731(volInfo, child->extentNumber);
                if (rc <= 0)
                    return rc;

                wcSeekSet(volInfo, currPos);
            }

            if (needToCopy) {
                if (FILETW_PTR(child)->onImage)
                    /* copy file from original image to new one */
                {
                    lseek(volInfo->imageForReading, FILETW_PTR(child)->offset,
                          SEEK_SET);

                    rc = writeByteBlockFromFile(volInfo->imageForReading,
                                                volInfo, FILETW_PTR(child)->size);
                    if (rc < 0)
                        return rc;
                } else
                    /* copy file from fs to new image */
                {
                    /* UPDATE the file's size, in case it's changed since we added it */
                    struct stat statStruct;

                    rc = stat(FILETW_PTR(child)->pathAndName, &statStruct);
                    if (rc != 0)
                        return BKERROR_STAT_FAILED;

                    FILETW_PTR(child)->size = statStruct.st_size;
                    /* UPDATE the file's size, in case it's changed since we added it */

                    srcFile = open(FILETW_PTR(child)->pathAndName, O_RDONLY);
                    if (srcFile == -1)
                        return BKERROR_OPEN_READ_FAILED;

                    rc = writeByteBlockFromFile(srcFile,
                                                volInfo, FILETW_PTR(child)->size);
                    if (rc < 0) {
                        close(srcFile);
                        return rc;
                    }

                    rc = close(srcFile);
                    if (rc < 0)
                        return BKERROR_EXOTIC;
                }

                /* fill extent with zeroes */
                numUnusedBytes = NBYTES_LOGICAL_BLOCK -
                                 wcSeekTell(volInfo) % NBYTES_LOGICAL_BLOCK;
                rc = writeByteBlock(volInfo, 0x00, numUnusedBytes);
                if (rc < 0)
                    return rc;
            }

            endPos = wcSeekTell(volInfo);

            if (volInfo->bootMediaType != BOOT_MEDIA_NONE &&
                    volInfo->bootRecordIsVisible &&
                    FILETW_PTR(child)->origFile == volInfo->bootRecordOnImage)
                /* this file is the boot record. assume it's isolinux and write the
                * boot info table */
            {
                unsigned char bootInfoTable[56];
                unsigned checksum;

                memset(bootInfoTable, 0, 56);

                /* go to the offset in the file where the boot info table is */
                wcSeekSet(volInfo, child->extentNumber *
                          NBYTES_LOGICAL_BLOCK + 8);

                /* sector number of pvd */
                write731ToByteArray(bootInfoTable, 16);
                /* sector number of boot file (this one) */
                write731ToByteArray(bootInfoTable + 4, child->extentNumber);
                /* boot file length in bytes */
                write731ToByteArray(bootInfoTable + 8, FILETW_PTR(child)->size);
                /* 32 bit checksum (the sum of all the 32-bit words in the boot
                * file starting at byte offset 64 */
                rc = bootInfoTableChecksum(volInfo->imageForReading, FILETW_PTR(child), &checksum);
                if (rc <= 0)
                    return rc;
                write731ToByteArray(bootInfoTable + 12, checksum);
                /* the rest is reserved, leave at zero */

                rc = wcWrite(volInfo, (char*)bootInfoTable, 56);
                if (rc <= 0)
                    return rc;
            }

            /* WRITE file location and size */
            wcSeekSet(volInfo, child->extentLocationOffset);

            rc = write733(volInfo, child->extentNumber);
            if (rc <= 0)
                return rc;

            rc = write733(volInfo, FILETW_PTR(child)->size);
            if (rc <= 0)
                return rc;

            if (filenameTypes & FNTYPE_JOLIET)
                /* also update location and size on joliet tree */
            {
                wcSeekSet(volInfo, child->extentLocationOffset2);

                rc = write733(volInfo, child->extentNumber);
                if (rc <= 0)
                    return rc;

                rc = write733(volInfo, FILETW_PTR(child)->size);
                if (rc <= 0)
                    return rc;
            }

            wcSeekSet(volInfo, endPos);
            /* END WRITE file location and size */
        } else if (IS_DIR(child->posixFileMode)) {
            rc = writeFileContents(volInfo, DIRTW_PTR(child), filenameTypes);
            if (rc < 0)
                return rc;
        } else if (IS_SYMLINK(child->posixFileMode)) {
            /* WRITE symlink location and size (0) */
            endPos = wcSeekTell(volInfo);

            wcSeekSet(volInfo, child->extentLocationOffset);

            rc = write733(volInfo, 0);
            if (rc <= 0)
                return rc;

            rc = write733(volInfo, 0);
            if (rc <= 0)
                return rc;

            if (filenameTypes & FNTYPE_JOLIET)
                /* also update location and size on joliet tree */
            {
                wcSeekSet(volInfo, child->extentLocationOffset2);

                rc = write733(volInfo, 0);
                if (rc <= 0)
                    return rc;

                rc = write733(volInfo, 0);
                if (rc <= 0)
                    return rc;
            }

            wcSeekSet(volInfo, endPos);
            /* END WRITE symlink location and size (0)  */
        }

        child = child->next;

    } /* while(nextFile != NULL) */

    return 1;
}

/* field size must be even. !!check all calls to make sure */
int writeJolietStringField(VolInfo* volInfo, const char* name, int fieldSize)
{
    char jolietName[512]; /* don't see why would ever want
                          * to write a longer one */
    int srcCount;
    int destCount;
    int rc;

    srcCount = 0;
    destCount = 0;
    while (name[srcCount] != '\0' && destCount < fieldSize) {
        /* first byte zero */
        jolietName[destCount] = 0x00;
        /* second byte character */
        jolietName[destCount + 1] = name[srcCount];

        srcCount += 1;
        destCount += 2;
    }

    while (destCount < fieldSize)
        /* pad with ucs2 spaces */
    {
        jolietName[destCount] = 0x00;
        jolietName[destCount + 1] = ' ';

        destCount += 2;
    }

    rc = wcWrite(volInfo, jolietName, destCount);
    if (rc <= 0)
        return rc;

    return 1;
}

/* write NM that won't fit in a directory record */
int writeLongNM(VolInfo* volInfo, BaseToWrite* node)
{
    off_t startPos;
    int fullNameLen;
    unsigned char CErecord[28];
    bool fitsInOneNM;
    int firstNMlen;
    off_t endPos;
    int rc;
    int lenOfCE;

    startPos = wcSeekTell(volInfo);

    fullNameLen = strlen(node->nameRock);

    /* should have checked for this before getting into this function */
    if (fullNameLen > 255)
        return BKERROR_SANITY;

    if (fullNameLen > 250) {
        fitsInOneNM = false;
        firstNMlen = 250;
    } else {
        fitsInOneNM = true;
        firstNMlen = fullNameLen;
    }

    /* NM record(s) */
    if (fitsInOneNM) {
        rc = writeRockNM(volInfo, node->nameRock, firstNMlen, false);
        if (rc <= 0)
            return rc;
    } else {
        rc = writeRockNM(volInfo, node->nameRock, firstNMlen, true);
        if (rc <= 0)
            return rc;
        rc = writeRockNM(volInfo, node->nameRock + firstNMlen, fullNameLen - firstNMlen, false);
        if (rc <= 0)
            return rc;
    }

    lenOfCE = wcSeekTell(volInfo) - startPos;

    /* write blank to conclude extent */
    rc = writeByteBlock(volInfo, 0x00, NBYTES_LOGICAL_BLOCK -
                        wcSeekTell(volInfo) % NBYTES_LOGICAL_BLOCK);
    if (rc < 0)
        return rc;

    endPos = wcSeekTell(volInfo);

    /* CE record back in the directory record */
    wcSeekSet(volInfo, node->offsetForCE);

    CErecord[0] = 'C';
    CErecord[1] = 'E';
    CErecord[2] = 28; /* length */
    CErecord[3] = 1; /* version */
    write733ToByteArray(CErecord + 4, startPos / NBYTES_LOGICAL_BLOCK); /* block location */
    /* i am always using 1 logical block per name */
    write733ToByteArray(CErecord + 12, 0); /* offset to start */
    write733ToByteArray(CErecord + 20, lenOfCE); /* length */

    rc = wcWrite(volInfo, (char*)CErecord, CErecord[2]);
    if (rc <= 0)
        return rc;
    /* END CE record back in the directory record */

    wcSeekSet(volInfo, endPos);

    return 1;
}

/* write all NMs in the tree that won't fit in directory records */
int writeLongNMsInDir(VolInfo* volInfo, DirToWrite* dir)
{
    BaseToWrite* child;
    int rc;

    child = dir->children;
    while (child != NULL) {
        if (child->offsetForCE != 0) {
            rc = writeLongNM(volInfo, child);
            if (rc <= 0)
                return rc;
        }

        if (IS_DIR(child->posixFileMode)) {
            rc = writeLongNMsInDir(volInfo, DIRTW_PTR(child));
            if (rc <= 0)
                return rc;
        }

        child = child->next;
    }

    return 1;
}

/* returns path table size (number of bytes not counting the blank) */
int writePathTable(VolInfo* volInfo, const DirToWrite* tree, bool isTypeL,
                   int filenameType)
{
    int treeHeight;
    int count;
    int level;
    int* dirsPerLevel; /* a dynamic array of the number of dirs per level */
    int numDirsSoFar;
    off_t origPos;
    int numBytesWritten;
    int rc;

    origPos = wcSeekTell(volInfo);

    if (origPos % NBYTES_LOGICAL_BLOCK != 0)
        return BKERROR_SANITY;

    treeHeight = countTreeHeight(tree, 1);

    dirsPerLevel = malloc(sizeof(int) * treeHeight);
    if (dirsPerLevel == NULL)
        return BKERROR_OUT_OF_MEMORY;

    for (count = 0; count < treeHeight; count++) {
        dirsPerLevel[count] = countDirsOnLevel(tree, count + 1, 1);
    }

    for (level = 1; level <= treeHeight; level++) {
        if (level == 1)
            /* numDirsSoFar = parent dir num */
            numDirsSoFar = 1;
        else if (level == 2)
            numDirsSoFar = 1;
        else {
            /* ex. when i am on level 4 i want number of dirs on levels 1 + 2 */
            numDirsSoFar = 0;
            for (count = 0; count < level - 2; count++) {
                numDirsSoFar += dirsPerLevel[count];
            }
        }

        rc = writePathTableRecordsOnLevel(volInfo, tree, isTypeL, filenameType,
                                          level, 1, &numDirsSoFar);
        if (rc < 0) {
            free(dirsPerLevel);
            return rc;
        }
    }

    numBytesWritten = wcSeekTell(volInfo) - origPos;

    /* blank to conclude extent */
    rc = writeByteBlock(volInfo, 0x00, NBYTES_LOGICAL_BLOCK -
                        numBytesWritten % NBYTES_LOGICAL_BLOCK);
    if (rc < 0) {
        free(dirsPerLevel);
        return rc;
    }

    free(dirsPerLevel);

    return numBytesWritten;
}

int writePathTableRecordsOnLevel(VolInfo* volInfo, const DirToWrite* dir,
                                 bool isTypeL, int filenameType,
                                 int targetLevel, int thisLevel,
                                 int* parentDirNum)
{
    int rc;
    BaseToWrite* child;

    unsigned char fileIdLen;
    unsigned char byte;
    unsigned exentLocation;
    unsigned short parentDirId; /* copy of *parentDirNum */
    static const char rootId = 0x00;

    if (thisLevel == targetLevel)
        /* write path table record */
    {
        /* LENGTH  of directory identifier */
        if (targetLevel == 1)
            /* root */
            fileIdLen = 1;
        else {
            if (filenameType & FNTYPE_JOLIET) {
                fileIdLen = 2 * strlen(BASETW_PTR(dir)->nameJoliet);
            } else {
                fileIdLen = strlen(BASETW_PTR(dir)->name9660);
            }
        }

        rc = write711(volInfo, fileIdLen);
        if (rc <= 0)
            return rc;
        /* END LENGTH  of directory identifier */

        /* extended attribute record length */
        byte = 0;
        rc = write711(volInfo, byte);
        if (rc <= 0)
            return rc;

        /* LOCATION of extent */
        if (filenameType & FNTYPE_JOLIET)
            exentLocation = dir->extentNumber2;
        else
            exentLocation = BASETW_PTR(dir)->extentNumber;

        if (isTypeL)
            rc = write731(volInfo, exentLocation);
        else
            rc = write732(volInfo, exentLocation);
        if (rc <= 0)
            return rc;
        /* END LOCATION of extent */

        /* PARENT directory number */
        parentDirId = *parentDirNum;

        if (isTypeL)
            rc = write721(volInfo, parentDirId);
        else
            rc = write722(volInfo, parentDirId);

        if (rc <= 0)
            return rc;
        /* END PARENT directory number */

        /* DIRECTORY identifier */
        if (targetLevel == 1)
            /* root */
        {
            rc = wcWrite(volInfo, &rootId, 1);
            if (rc <= 0)
                return rc;
        } else {
            if (filenameType & FNTYPE_JOLIET) {
                rc = writeJolietStringField(volInfo, BASETW_PTR(dir)->nameJoliet, fileIdLen);
                if (rc < 0)
                    return rc;
            } else {
                rc = wcWrite(volInfo, BASETW_PTR(dir)->name9660, fileIdLen);
                if (rc <= 0)
                    return rc;
            }
        }
        /* END DIRECTORY identifier */

        /* padding field */
        if (fileIdLen % 2 != 0) {
            byte = 0;
            rc = write711(volInfo, byte);
            if (rc <= 0)
                return rc;
        }

    } else { /* if(thisLevel < targetLevel) */
        child = dir->children;
        while (child != NULL) {
            if (IS_DIR(child->posixFileMode)) {
                if (thisLevel == targetLevel - 2)
                    /* am now going through the list of dirs where the parent is */
                {
                    if (targetLevel != 2)
                        /* first and second level have the same parent: 1 */
                    {
                        (*parentDirNum)++;
                    }
                }

                rc = writePathTableRecordsOnLevel(volInfo, DIRTW_PTR(child), isTypeL,
                                                  filenameType, targetLevel,
                                                  thisLevel + 1, parentDirNum);
                if (rc < 0)
                    return rc;
            }

            child = child->next;
        }
    }

    return 1;
}

/* This doesn't need support for CE because it's only written in one place,
* the root 'self' directory record. */
int writeRockER(VolInfo* volInfo)
{
    int rc;
    char record[46];

    /* identification */
    record[0] = 'E';
    record[1] = 'R';

    /* record length */
    record[2] = 46;

    /* entry version */
    record[3] = 1;

    /* extension identifier length */
    record[4] = 10;

    /* extension descriptor length */
    record[5] = 10;

    /* extension source length */
    record[6] = 18;

    /* extension version */
    record[7] = 1;

    /* extension identifier */
    strncpy(&(record[8]), "IEEE_P1282", 10);

    /* extension descriptor */
    strncpy(&(record[18]), "DRAFT_1_12", 10);

    /* extension source */
    strncpy(&(record[28]), "ADOPTED_1994_07_08", 18);

    rc = wcWrite(volInfo, record, 46);
    if (rc <= 0)
        return rc;

    return 1;
}

int writeRockNM(VolInfo* volInfo, char* name, int nameLen, bool doesContinue)
{
    int rc;
    char recordStart[5];

    /* identification */
    recordStart[0] = 'N';
    recordStart[1] = 'M';

    /* record length */
    recordStart[2] = 5 + nameLen;

    /* entry version */
    recordStart[3] = 1;

    /* flags */
    if (doesContinue)
        recordStart[4] = 0x01;
    else
        recordStart[4] = 0;

    rc = wcWrite(volInfo, recordStart, 5);
    if (rc <= 0)
        return rc;

    rc = wcWrite(volInfo, name, nameLen);
    if (rc <= 0)
        return rc;

    return 1;
}

/* the slackware cd has 36 byte PX entries, missing the file serial number
* so i will do the same */
int writeRockPX(VolInfo* volInfo, unsigned posixFileMode, bool isADir)
{
    int rc;
    unsigned char record[36];
    unsigned posixFileLinks;

    /* identification */
    record[0] = 'P';
    record[1] = 'X';

    /* record length */
    record[2] = 36;

    /* entry version */
    record[3] = 1;

    /* posix file mode */
    write733ToByteArray(&(record[4]), posixFileMode);

    /* POSIX file links */
    /*
    * this i think is number of subdirectories + 2 (self and parent)
    * and 1 for a file
    * it's probably not used on read-only filesystems
    * to add it, i would need to pass the number of links in a parent dir
    * recursively in writeDir(). brrrrr.
    */
    if (isADir)
        posixFileLinks = 2;
    else
        posixFileLinks = 1;

    write733ToByteArray(&(record[12]), posixFileLinks);
    /* END POSIX file links */

    /* posix file user id, posix file group id */
    memset(&(record[20]), 0, 16);

    rc = wcWrite(volInfo, (char*)record, 36);
    if (rc <= 0)
        return rc;

    return 1;
}

int writeRockSL(VolInfo* volInfo, SymLinkToWrite* symlink, bool doWrite)
{
    int stringCount;
    int targetLen;
    int numBytesNeeded;
    int numBytesToSkip;
    unsigned char* record;
    int recordCount;
    int rc;

    targetLen = strlen(symlink->target);

    /* figure out how much room i need */
    numBytesNeeded = 0;
    numBytesToSkip = 0;
    stringCount = 0;
    while (stringCount < targetLen) {
        int numBytesToSkip;
        char* nextSlash;

        if (symlink->target[stringCount] == '/')
            /* root (/) */
        {
            numBytesNeeded += 2;
            numBytesToSkip = 1;
        } else if (symlink->target[stringCount] == '.' &&
                   (stringCount + 1 == targetLen || symlink->target[stringCount + 1] == '/'))
            /* current (.) */
        {
            numBytesNeeded += 2;
            numBytesToSkip = 2;
        } else if (symlink->target[stringCount] == '.' &&
                   stringCount + 1 < targetLen && symlink->target[stringCount + 1] == '.')
            /* parent (..) */
        {
            numBytesNeeded += 2;
            numBytesToSkip = 3;
        } else
            /* regular filename */
        {
            nextSlash = strchr(symlink->target + stringCount, '/');
            if (nextSlash != NULL)
                numBytesToSkip = nextSlash - (symlink->target + stringCount);
            else
                numBytesToSkip = targetLen - stringCount;

            numBytesNeeded += 2 + numBytesToSkip;

            numBytesToSkip += 1;
        }

        stringCount += numBytesToSkip;
    }

    if (!doWrite)
        return 5 + numBytesNeeded;

    if (numBytesNeeded > NCHARS_SYMLINK_TARGET_MAX - 1)
        return BKERROR_SYMLINK_TARGET_TOO_LONG;

    record = malloc(5 + numBytesNeeded);
    if (record == NULL)
        return BKERROR_OUT_OF_MEMORY;

    record[0] = 'S';
    record[1] = 'L';
    record[2] = 5 + numBytesNeeded; /* length */
    record[3] = 1; /* version */
    record[4] = 0x00; /* flags */

    /* write SL */
    numBytesToSkip = 0;
    stringCount = 0;
    recordCount = 5;
    while (stringCount < targetLen) {
        int numBytesToSkip;
        char* nextSlash;

        if (symlink->target[stringCount] == '/')
            /* root (/) */
        {
            numBytesToSkip = 1;
            record[recordCount] = 0x08;
            record[recordCount + 1] = 0;
            recordCount += 2;
        } else if (symlink->target[stringCount] == '.' &&
                   (stringCount + 1 == targetLen || symlink->target[stringCount + 1] == '/'))
            /* current (.) */
        {
            numBytesToSkip = 2;
            record[recordCount] = 0x02;
            record[recordCount + 1] = 0;
            recordCount += 2;
        } else if (symlink->target[stringCount] == '.' &&
                   stringCount + 1 < targetLen && symlink->target[stringCount + 1] == '.')
            /* parent (..) */
        {
            numBytesToSkip = 3;
            record[recordCount] = 0x04;
            record[recordCount + 1] = 0;
            recordCount += 2;
        } else
            /* regular filename */
        {
            nextSlash = strchr(symlink->target + stringCount, '/');
            if (nextSlash != NULL)
                numBytesToSkip = nextSlash - (symlink->target + stringCount);
            else
                numBytesToSkip = targetLen - stringCount;

            record[recordCount] = 0x00;
            record[recordCount + 1] = numBytesToSkip;
            strncpy((char*)record + recordCount + 2, symlink->target + stringCount, numBytesToSkip);
            recordCount += 2 + numBytesToSkip;

            numBytesToSkip += 1;
        }

        /* + separator */
        stringCount += numBytesToSkip;
    }

    if (recordCount != numBytesNeeded + 5) {
        free(record);
        return BKERROR_SANITY;
    }

    rc = wcWrite(volInfo, (char*)record, recordCount);
    if (rc <= 0) {
        free(record);
        return rc;
    }

    free(record);

    return 5 + numBytesNeeded;
}

/* This doesn't need support for CE because it's only written in one place,
* the root 'self' directory record. */
int writeRockSP(VolInfo* volInfo)
{
    int rc;
    unsigned char record[7];

    /* identification */
    record[0] = 'S';
    record[1] = 'P';

    /* record length */
    record[2] = 7;

    /* entry version */
    record[3] = 1;

    /* check bytes */
    record[4] = 0xBE;
    record[5] = 0xEF;

    /* bytes skipped */
    record[6] = 0;

    rc = wcWrite(volInfo, (char*)record, 7);
    if (rc <= 0)
        return rc;

    return 1;
}

int writeVdsetTerminator(VolInfo* volInfo)
{
    int rc;
    unsigned char byte;
    unsigned char aString[6];

    /* volume descriptor type */
    byte = 255;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* standard identifier */
    strcpy((char*)aString, "CD001");
    rc = wcWrite(volInfo, (char*)aString, 5);
    if (rc <= 0)
        return rc;

    /* volume descriptor version */
    byte = 1;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    rc = writeByteBlock(volInfo, 0, 2041);
    if (rc < 0)
        return rc;

    return 1;
}

/*
* -has to be called after the files were written so that the
*  volume size is recorded properly
* -rootdr location, size are in bytes
* -note strings are not terminated on image
*/
int writeVolDescriptor(VolInfo* volInfo, off_t rootDrLocation,
                       unsigned rootDrSize, off_t lPathTableLoc,
                       off_t mPathTableLoc, unsigned pathTableSize,
                       time_t creationTime, bool isPrimary)
{
    int rc;
    int count;

    unsigned char byte;
    unsigned char aString[129];
    unsigned anUnsigned;
    unsigned short anUnsignedShort;
    size_t currPos;

    /* VOLUME descriptor type */
    if (isPrimary)
        byte = 1;
    else
        byte = 2;
    /* END VOLUME descriptor type */

    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* standard identifier */
    strcpy((char*)aString, "CD001");
    rc = wcWrite(volInfo, (char*)aString, 5);
    if (rc <= 0)
        return rc;

    /* volume descriptor version (always 1) */
    byte = 1;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* primary: unused field
    *  supplementary: volume flags, 0x00 */
    byte = 0;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* system identifier (32 spaces) */
    if (isPrimary) {
        strcpy((char*)aString, "                                ");
        rc = wcWrite(volInfo, (char*)aString, 32);
        if (rc <= 0)
            return rc;
    } else {
        rc = writeJolietStringField(volInfo, "", 32);
        if (rc < 0)
            return rc;
    }

    /* VOLUME identifier */
    if (isPrimary) {
        strcpy((char*)aString, volInfo->volId);

        for (count = strlen((char*)aString); count < 32; count++)
            aString[count] = ' ';

        rc = wcWrite(volInfo, (char*)aString, 32);
        if (rc <= 0)
            return rc;
    } else {
        rc = writeJolietStringField(volInfo, volInfo->volId, 32);
        if (rc < 0)
            return rc;
    }
    /* END VOLUME identifier */

    /* unused field */
    rc = writeByteBlock(volInfo, 0, 8);
    if (rc < 0)
        return rc;

    /* VOLUME space size (number of logical blocks, absolutely everything) */
    /* it's safe to not use wcSeek() here since everything is left as it is */
    currPos = lseek(volInfo->imageForWriting, 0, SEEK_CUR);

    lseek(volInfo->imageForWriting, 0, SEEK_END);
    anUnsigned = lseek(volInfo->imageForWriting, 0, SEEK_CUR) /
                 NBYTES_LOGICAL_BLOCK;

    lseek(volInfo->imageForWriting, currPos, SEEK_SET);

    rc = write733(volInfo, anUnsigned);
    if (rc <= 0)
        return rc;
    /* END VOLUME space size (number of logical blocks, absolutely everything) */

    /* primary: unused field
    *  joliet: escape sequences */
    if (isPrimary) {
        rc = writeByteBlock(volInfo, 0, 32);
        if (rc < 0)
            return rc;
    } else {
        /* this is the only joliet field that's padded with 0x00 instead of ' ' */
        aString[0] = 0x25;
        aString[1] = 0x2F;
        aString[2] = 0x45;

        rc = wcWrite(volInfo, (char*)aString, 3);
        if (rc <= 0)
            return rc;

        rc = writeByteBlock(volInfo, 0, 29);
        if (rc < 0)
            return rc;
    }

    /* volume set size (always 1) */
    anUnsignedShort = 1;
    rc = write723(volInfo, anUnsignedShort);
    if (rc <= 0)
        return rc;

    /* volume sequence number (also always 1) */
    rc = write723(volInfo, anUnsignedShort);
    if (rc <= 0)
        return rc;

    /* logical block size (always 2048) */
    anUnsignedShort = NBYTES_LOGICAL_BLOCK;
    rc = write723(volInfo, anUnsignedShort);
    if (rc <= 0)
        return rc;

    /* path table size */
    anUnsigned = pathTableSize;
    rc = write733(volInfo, anUnsigned);
    if (rc <= 0)
        return rc;

    /* location of occurrence of type l path table */
    anUnsigned = lPathTableLoc / NBYTES_LOGICAL_BLOCK;
    rc = write731(volInfo, anUnsigned);
    if (rc <= 0)
        return rc;

    /* location of optional occurrence of type l path table */
    anUnsigned = 0;
    rc = write731(volInfo, anUnsigned);
    if (rc <= 0)
        return rc;

    /* location of occurrence of type m path table */
    anUnsigned = mPathTableLoc / NBYTES_LOGICAL_BLOCK;
    rc = write732(volInfo, anUnsigned);
    if (rc <= 0)
        return rc;

    /* location of optional occurrence of type m path table */
    anUnsigned = 0;
    rc = write732(volInfo, anUnsigned);
    if (rc <= 0)
        return rc;

    /* ROOT dr */
    /* record length (always 34 here) */
    byte = 34;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* extended attribute record length (always none) */
    byte = 0;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* location of extent */
    anUnsigned = rootDrLocation / NBYTES_LOGICAL_BLOCK;
    rc = write733(volInfo, anUnsigned);
    if (rc <= 0)
        return rc;

    /* data length */
    rc = write733(volInfo, rootDrSize);
    if (rc <= 0)
        return rc;

    /* recording time */
    epochToShortString(creationTime, (char*)aString);
    rc = wcWrite(volInfo, (char*)aString, 7);
    if (rc <= 0)
        return rc;

    /* file flags (always binary 00000010 here) */
    byte = 0x02;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* file unit size (not in interleaved mode -> 0) */
    byte = 0;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* interleave gap size (not in interleaved mode -> 0) */
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* volume sequence number */
    anUnsignedShort = 1;
    rc = write723(volInfo, anUnsignedShort);
    if (rc <= 0)
        return rc;

    /* length of file identifier */
    byte = 1;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* file identifier */
    byte = 0;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;
    /* END ROOT dr */

    /* volume set identidier */
    if (isPrimary) {
        rc = writeByteBlock(volInfo, ' ', 128);
        if (rc < 0)
            return rc;
    } else {
        rc = writeJolietStringField(volInfo, "", 128);
        if (rc < 0)
            return rc;
    }

    /* PUBLISHER identifier */
    strcpy((char*)aString, volInfo->publisher);

    if (isPrimary) {
        for (count = strlen((char*)aString); count < 128; count++)
            aString[count] = ' ';

        rc = wcWrite(volInfo, (char*)aString, 128);
        if (rc <= 0)
            return rc;
    } else {
        rc = writeJolietStringField(volInfo, (char*)aString, 128);
        if (rc < 0)
            return rc;
    }
    /* PUBLISHER identifier */

    /* DATA preparer identifier */
    if (isPrimary) {
        rc = wcWrite(volInfo, "ISO Master", 10);
        if (rc <= 0)
            return rc;

        rc = writeByteBlock(volInfo, ' ', 118);
        if (rc < 0)
            return rc;
    } else {
        rc = writeJolietStringField(volInfo, "ISO Master", 128);
        if (rc < 0)
            return rc;
    }
    /* END DATA preparer identifier */

    /* application identifier, copyright file identifier, abstract file
    * identifier, bibliographic file identifier (128 + 3*37) */
    if (isPrimary) {
        rc = writeByteBlock(volInfo, ' ', 239);
        if (rc < 0)
            return rc;
    } else {
        /* application id */
        rc = writeJolietStringField(volInfo, "", 128);
        if (rc < 0)
            return rc;

        /* 18 ucs2 spaces + 0x00 */
        for (count = 0; count < 3; count++) {
            rc = writeJolietStringField(volInfo, "", 36);
            if (rc < 0)
                return rc;

            byte = 0x00;
            rc = wcWrite(volInfo, (char*) & byte, 1);
            if (rc <= 0)
                return rc;
        }
    }

    /* VOLUME creation date */
    epochToLongString(creationTime, (char*)aString);

    rc = wcWrite(volInfo, (char*)aString, 17);
    if (rc <= 0)
        return rc;
    /* END VOLUME creation date */

    /* volume modification date (same as creation) */
    rc = wcWrite(volInfo, (char*)aString, 17);
    if (rc <= 0)
        return rc;

    /* VOLUME expiration date (none) */
    rc = writeByteBlock(volInfo, '0', 16);
    if (rc < 0)
        return rc;

    byte = 0;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;
    /* END VOLUME expiration date (none) */

    /* volume effective date (same as creation) */
    rc = wcWrite(volInfo, (char*)aString, 17);
    if (rc <= 0)
        return rc;

    /* file structure version */
    byte = 1;
    rc = write711(volInfo, byte);
    if (rc <= 0)
        return rc;

    /* reserved, applications use, reserved */
    rc = writeByteBlock(volInfo, 0, 1166);
    if (rc < 0)
        return rc;

    return 1;
}
