/******************************* 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;
}
