/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2002 Kester Habermann - All Rights Reserved
 *   2002-08-30 Kester Habermann <kester@linuxtag.org>         
 *   Time-stamp: <2002-11-12 00:24:05 kester>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
 *   USA; either version 2 of the License, or (at your option) any later
 *   version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

// CHANGES
//   v0.04, 2002-11-11, Kester Habermann <kester@linuxtag.org>
//      * fixed fat-bug with hidden files
//
//   v0.03, 2002-11-11, Kester Habermann <kester@linuxtag.org>
//      * added --action export_floppy and --action import_floppy
//
//   v0.02, 2002-11-11, Kester Habermann <kester@linuxtag.org>
//      * fixed bug with to short local file names
//      * with action export_file "--local_file" now may be directory,
//        file with same name as --image_file will be created
//        ("." also is a directory)
//      * with action import_file "--image_file" now may "." and
//        local_file (without path) will be used
//      * local_file may be "-" with action export_file for STDOUT and
//        with action import_file for STDIN
//
// TOOD:
//   - allow files to shrink and grow
//   - test if sizes are ok
//   - proper header for all error messages
//   - copyright, version, author info
//   - multi languages
//   - renaming of files (to remember which kbd map is used)
//   - clean up output for verbose
//   - action to replace complete boot floppy image [DONE], read blockwise [TODO]
//   - shortcuts:
//     * edit boot options for userdef and set userdef as defaults
//     * set boot options for userdef
//     * set default boot
//     * change language (lang=xx + userdef default), keyboard-file, help-file

//   - add URL for El-Torito and FAT-Specification

// BUGs
//   - if given FAT name st longer than 8+3 chars it is truncated 
//     (should give error)


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <libgen.h>
#include <limits.h>

#define KNOPPIX_CUSTOMIZE_NAME          "knoppix-customize"
#define KNOPPIX_CUSTOMIZE_AUTHOR        "Kester Habermann <kester@linuxtag.org>"
#define KNOPPIX_CUSTOMIZE_VERSION       "v0.04 (2002-11-11)"

// Sector size for FAT12 and FAT16
#define FAT_SECTOR_SIZE                  0x200
#define FLOPPY_BOOT_SECTOR               (1* FAT_SECTOR_SIZE)

#define CD_SECTOR_SIZE                   0x800
#define CD_BOOT_RECORD_VOLUME_DESCRIPTOR (1 * CD_SECTOR_SIZE)
#define CD_BOOT_CAT_BUFFER               (1 * CD_SECTOR_SIZE)
// Boot-Disc Types
// Sides * Tracks * Sectors/Track * Bytes/Block
#define BOOT_IMG_BUFFER_1_2              (2 * 15 * 80 * 512)
#define BOOT_IMG_BUFFER_1_44             (2 * 18 * 80 * 512)
#define BOOT_IMG_BUFFER_2_88             (2 * 36 * 80 * 512)

#define FLOPPY_1_2                       "1.2 MB diskette"
#define FLOPPY_1_44                      "1.44 MB diskette"
#define FLOPPY_2_88                      "2.88 MB diskette"
#define ISO_9660_IDENTIFIER              "CD001"
#define CD_BOOT_SYSTEM_INDENTIFIER       "EL TORITO SPECIFICATION"
#define MANUFACTOR_MAXLEN                 24

#define FAT_FILENAME_LENGTH               11
#define FAT_ENTRY_SIZE                    32


// taken from /usr/include/glib-2.0/glib/gutils.h
#ifdef G_OS_WIN32

/* On native Win32, directory separator is the backslash, and search path
 * separator is the semicolon.
 */
#define G_DIR_SEPARATOR '\\'
#define G_DIR_SEPARATOR_S "\\"
#define G_SEARCHPATH_SEPARATOR ';'
#define G_SEARCHPATH_SEPARATOR_S ";"

#else  /* !G_OS_WIN32 */

/* Unix */

#define G_DIR_SEPARATOR '/'
#define G_DIR_SEPARATOR_S "/"
#define G_SEARCHPATH_SEPARATOR ':'
#define G_SEARCHPATH_SEPARATOR_S ":"

#endif /* !G_OS_WIN32 */


// one directory entry
struct dir_entry_t {
    unsigned char name[FAT_FILENAME_LENGTH + 1];
    long pos;
    long size;
};

// important data from boot floppy
struct boot_floppy_t {
    long boot_img_offset;
    size_t boot_img_size;
    int FirstRootDirSecNum;
    int BytesPerCluster;
    struct dir_entry_t *fat_root_dir;
    int fat_root_dir_size;
};

// Global variables
int opt_verbose = 0;

int find_boot_image(FILE *fh, struct stat stat_buf, struct boot_floppy_t *boot_floppy) {
    unsigned char *floppy_boot_sector;    // buffer for boot record volume descriptor
    unsigned char *cd_boot_rvd;           // buffer for boot record volume descriptor
    unsigned char *cd_boot_cat;           // buffer for boot catalog
    char cd_manufactor[MANUFACTOR_MAXLEN];
    long cd_boot_cat_ptr;
    long cd_boot_img_ptr;

    // allocate read_buffer for reading boot record volume descriptor
    if ((cd_boot_rvd = malloc((size_t) (CD_BOOT_RECORD_VOLUME_DESCRIPTOR))) == NULL) {
        fprintf(stderr, "Error mallocing CD boot record volume descriptor\n");
        return 0;
    }

    // allocate read_buffer for reading boot catalog
    if ((cd_boot_cat = malloc((size_t) (CD_BOOT_CAT_BUFFER))) == NULL) {
        fprintf(stderr, "Error mallocing CD boot cat buffer\n");
        return 0;
    }

    // allocate read_buffer for reading floppy boot sector
    if ((floppy_boot_sector = malloc((size_t) (FLOPPY_BOOT_SECTOR))) == NULL) {
        fprintf(stderr, "Error mallocing floppy boot sector buffer\n");
        return 0;
    }

    // read first sectors of size FAT_SECTOR, to see if it's FAT
    if (fread(((void *) floppy_boot_sector),
              (size_t) FLOPPY_BOOT_SECTOR,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error reading first sector from image\n");
        return 0;
    }

    // Test whether image ist FAT, this is the case if someone chooses
    // to only modify a boot disk and not the whole book image
    // Search for FAT-Magic 0x55AA

    if (floppy_boot_sector[0x1fe] == 0x55 && floppy_boot_sector[0x1ff] == 0xAA) {
        if (opt_verbose) printf("Image is FAT, assuming floppy\n");
        boot_floppy->boot_img_offset = 0;
        // find out actual size of disk image
        switch (stat_buf.st_size) {
            case BOOT_IMG_BUFFER_1_2:  if (opt_verbose) printf("Image is %s\n", FLOPPY_1_2);
                                       boot_floppy->boot_img_size = BOOT_IMG_BUFFER_1_2;
                                       break;

            case BOOT_IMG_BUFFER_1_44: if (opt_verbose) printf("Image is %s\n", FLOPPY_1_44);
                                       boot_floppy->boot_img_size = BOOT_IMG_BUFFER_1_44;
                                       break;

            case BOOT_IMG_BUFFER_2_88: if (opt_verbose) printf("Image is %s\n", FLOPPY_2_88);
                                       boot_floppy->boot_img_size = BOOT_IMG_BUFFER_2_88;
                                       break;

            default:                   fprintf(stderr, "Invalid disk image size (%d)\n", stat_buf.st_size);
                                       return 0;
                                       break;
        }
        return 1;
    } 

    //
    // Read CD-Image
    //
    if (opt_verbose) printf("Image is not FAT, assuming ISO\n");
    // read boot volume descriptor
    if (fseek(fh, (long) (0x11 * CD_SECTOR_SIZE), SEEK_SET) != 0) {
        fprintf(stderr, "Error seeking into file\n");
        return 0;
    }
    
    if (fread(((void *) cd_boot_rvd),
              (size_t) CD_BOOT_RECORD_VOLUME_DESCRIPTOR,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error reading cd boot volume descriptor\n");
        return 0;
    }
    
    if (cd_boot_rvd[0x00] != 0) {
        fprintf(stderr, "Invalid CD Boot Record Indicator\n");
        return 0;
    }
    
    if (strncmp(cd_boot_rvd + 0x01, ISO_9660_IDENTIFIER, (size_t) sizeof(ISO_9660_IDENTIFIER) != 0)) {
        fprintf(stderr, "Not ISO 9660 image\n");
        return 0;
    }
    
    if (strncmp(cd_boot_rvd + 0x07, CD_BOOT_SYSTEM_INDENTIFIER, (size_t) sizeof(CD_BOOT_SYSTEM_INDENTIFIER) != 0)) {
        fprintf(stderr, "Not El Torito Bootable\n");
        return 0;
    }  
    
    cd_boot_cat_ptr =   ( cd_boot_rvd[0x47 + 3] << 24 )
                      + ( cd_boot_rvd[0x47 + 2] << 16 )
                      + ( cd_boot_rvd[0x47 + 1] <<  8 )
                      + ( cd_boot_rvd[0x47 + 0]       );
    
    if (opt_verbose) printf("boot cat ptr: 0x%.8x x 0x800 (0x%.8x)\n", cd_boot_cat_ptr, CD_SECTOR_SIZE * cd_boot_cat_ptr);
    
    // seek to boot catalog
    fseek(fh, (long) CD_SECTOR_SIZE * cd_boot_cat_ptr, SEEK_SET);
    
    if (!(fread(((void *) cd_boot_cat),
                (size_t) CD_BOOT_CAT_BUFFER,
                (size_t) 1,
                fh))) {
        fprintf(stderr, "Error reading CD boot cat\n");
        return 0;
    }
    
    if (cd_boot_cat[0] != 01) {
        fprintf(stderr, "Header ID, must be 01\n");
        return 0;
    }
    
    switch(cd_boot_cat[1]) {
        case 0:  if (opt_verbose) printf("Platform 80x86\n");
                 break;
        case 1:  if (opt_verbose) printf("Platform Power PC\n");
                 break;
        case 2:  if (opt_verbose) printf("Platform MAC\n");
                 break;
        default: fprintf(stderr, "Platform unknown\n");
                 return 0;
                 break;
    }
    
    strncpy(cd_manufactor, cd_boot_cat + 0x04, MANUFACTOR_MAXLEN);
    if (opt_verbose) printf("Manufactor: %s\n", cd_manufactor);
    
    switch (cd_boot_cat[0x20 + 0x00]) {
        case 0x00: fprintf(stderr, "CD Not Bootable!\n");
                   return 0;
                   break;
        case 0x88: if (opt_verbose) printf("CD Bootable!\n");
                   break;
        default:   fprintf(stderr, "Invalid Boot Indicator on CD\n");
                   return 0;
                   break;
    }

    if (opt_verbose) printf("Boot media type on CD: ");
    switch (cd_boot_cat[0x20 + 0x01]) {
        case 0x00: fprintf(stderr, "No emulation (can't handle this)\n");
                   return 0;
                   break;
        case 0x01: if (opt_verbose) printf("%s\n", FLOPPY_1_2);
                   boot_floppy->boot_img_size = BOOT_IMG_BUFFER_1_2;
                   break;
        case 0x02: if (opt_verbose) printf("%s\n", FLOPPY_1_44);
                   boot_floppy->boot_img_size = BOOT_IMG_BUFFER_1_44;
                   break;
        case 0x03: if (opt_verbose) printf("%s\n", FLOPPY_2_88);
                   boot_floppy->boot_img_size = BOOT_IMG_BUFFER_2_88;
                   break;
        case 0x04: fprintf(stderr, "Harddisk (can't handle this)\n");
                   return 0;
                   break;
        default:   fprintf(stderr, "Invalid Boot media type\n");
                   return 0;
                   break;
    }

    cd_boot_img_ptr =   ( cd_boot_cat[0x20 + 0x08 + 3] << 24 )
                      + ( cd_boot_cat[0x20 + 0x08 + 2] << 16 )
                      + ( cd_boot_cat[0x20 + 0x08 + 1] <<  8 )
                      + ( cd_boot_cat[0x20 + 0x08 + 0]       );

    boot_floppy->boot_img_offset = CD_SECTOR_SIZE * cd_boot_img_ptr;

    if (opt_verbose) printf("boot img ptr: 0x%.8x x 0x800 (0x%.8x)\n", cd_boot_img_ptr, boot_floppy->boot_img_offset);

    return 1;
}

int get_fat_root_dir (FILE *fh, struct boot_floppy_t *boot_floppy) {
    FILE *fh2;
    unsigned char *boot_img;              // buffer for boot image
    unsigned char *floppy_boot_sector;    // buffer for boot record volume descriptor
    unsigned int BPB_BytsPerSec;
    unsigned char BPB_SecPerClus;
    unsigned int BPB_RsvdSecCnt;
    unsigned char BPB_NumFATs;
    unsigned int BPB_FATSz16;
    unsigned char *fat_root_dir_buffer;
    int fat_root_dir_buffer_size;
    int i, j;

    //
    // Read Floppy-Image
    //

    // allocate read_buffer for reading boot image
    if ((boot_img = malloc(boot_floppy->boot_img_size)) == NULL) {
        fprintf(stderr, "Error mallocing CD boot img buffer\n");
        return 0;
    }

    if (opt_verbose) printf("\nReading boot image ...\n");
    // seek to boot image
    fseek(fh, boot_floppy->boot_img_offset, SEEK_SET);

    // allocate read_buffer for reading floppy boot sector
    if ((floppy_boot_sector = malloc((size_t) (FLOPPY_BOOT_SECTOR))) == NULL) {
        fprintf(stderr, "Error mallocing floppy boot sector buffer\n");
        return 0;
    }

    // read first floppy boot sector
    if (fread(((void *) floppy_boot_sector),
              (size_t) FLOPPY_BOOT_SECTOR,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error reading boot sector from image\n");
        return 0;
    }

    BPB_BytsPerSec =   ( floppy_boot_sector[12] << 8 )
                     + ( floppy_boot_sector[11]      );

    BPB_SecPerClus =     floppy_boot_sector[13];

    boot_floppy->BytesPerCluster = BPB_SecPerClus * BPB_BytsPerSec;

    BPB_RsvdSecCnt =   ( floppy_boot_sector[15] << 8 )
                     + ( floppy_boot_sector[14]      );

    BPB_NumFATs    =     floppy_boot_sector[16];

    BPB_FATSz16    =   ( floppy_boot_sector[23] << 8 )
                     + ( floppy_boot_sector[22]      );

    fat_root_dir_buffer_size  = BPB_NumFATs * BPB_BytsPerSec;
    boot_floppy->FirstRootDirSecNum = BPB_RsvdSecCnt + (BPB_NumFATs * BPB_FATSz16);

    if (opt_verbose) printf("BPB_BytsPerSec:     %d\n", BPB_BytsPerSec);
    if (opt_verbose) printf("BPB_SecPerClus:     %d\n", BPB_SecPerClus);
    if (opt_verbose) printf("BPB_RsvdSecCnt:     %d\n", BPB_RsvdSecCnt);
    if (opt_verbose) printf("BPB_NumFATs:        %d\n", BPB_NumFATs);
    if (opt_verbose) printf("BPB_FATSz16:        %d\n", BPB_FATSz16);

    if (opt_verbose) printf("\n");

    if (opt_verbose) printf("FAT Root Dir Buffer Size:  %d\n", fat_root_dir_buffer_size);
    if (opt_verbose) printf("FirstRootDirSecNum:        %d\n", boot_floppy->FirstRootDirSecNum);

    if (opt_verbose) printf("\n");

    // allocate buffer for FAT root dir
    if ((fat_root_dir_buffer = malloc((size_t) (fat_root_dir_buffer_size))) == NULL) {
        fprintf(stderr, "Error mallocing for FAT root-Rootdir\n");
        return 0;
    }

    // seek to FirstRootDirSecNum
    if (fseek(fh, boot_floppy->boot_img_offset + (long) (boot_floppy->FirstRootDirSecNum * BPB_BytsPerSec), SEEK_SET) != 0) {
        fprintf(stderr, "Error seeking into file\n");
        return 0;
    }
    
    // read FirstRootDirSecNum
    if (fread(((void *) fat_root_dir_buffer),
              (size_t) fat_root_dir_buffer_size,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error fat root dir\n");
        return 0;
    }

    // Count directory entries
    boot_floppy->fat_root_dir_size = 0;
    for (i = 0; i <= fat_root_dir_buffer_size - FAT_ENTRY_SIZE; i += FAT_ENTRY_SIZE) {
	// empty entry
	if (fat_root_dir_buffer[i] == 0xE5)
	    continue;

	// hidden entry
	if (fat_root_dir_buffer[i + 11] & 0x0f)
	    continue;

	// no more entries
	if (fat_root_dir_buffer[i] == 0x00)
	    break;

	boot_floppy->fat_root_dir_size++;
    }

    if (opt_verbose) printf("FAT root dir size:  %d\n", boot_floppy->fat_root_dir_size);

    // allocate buffer for FAT root dir
    if ((boot_floppy->fat_root_dir = malloc((size_t) (boot_floppy->fat_root_dir_size * FAT_ENTRY_SIZE))) == NULL) {
        fprintf(stderr, "Error mallocing for FAT root-Rootdir\n");
        return 0;
    }

    j = 0;
    for (i = 0; i <= fat_root_dir_buffer_size - FAT_ENTRY_SIZE; i += FAT_ENTRY_SIZE) {
	// empty entry
	if (fat_root_dir_buffer[i] == 0xE5)
	    continue;

	// hidden entry
	if (fat_root_dir_buffer[i + 11] & 0x0f)
	    continue;

	// no more entries
	if (fat_root_dir_buffer[i] == 0x00)
	    break;

	strncpy(boot_floppy->fat_root_dir[j].name, fat_root_dir_buffer + i, (size_t) FAT_FILENAME_LENGTH);
	boot_floppy->fat_root_dir[j].name[FAT_FILENAME_LENGTH] = '\0';

	boot_floppy->fat_root_dir[j].pos  =   ( fat_root_dir_buffer[i + 21] << 24 )
                                            + ( fat_root_dir_buffer[i + 20] << 16 )
				            + ( fat_root_dir_buffer[i + 27] <<  8 )
				            + ( fat_root_dir_buffer[i + 26]       );
	    
	boot_floppy->fat_root_dir[j].size =   ( fat_root_dir_buffer[i + 31] << 24 )
				            + ( fat_root_dir_buffer[i + 30] << 16 )
				            + ( fat_root_dir_buffer[i + 29] <<  8 )
				            + ( fat_root_dir_buffer[i + 28]       );
	j++;
    }

    free(fat_root_dir_buffer);

    return 1;
}

// convert file name to fat file name
void filename2fat(unsigned char *dst, const unsigned char *src) {
    int i, j, dot_pos;

    // convert searched filename to fat filename
    // - uppercase
    // - remove dot
    // - fill gap and end with spaces 

    j = 0;
    for (i = 0; i <= strlen(src) - 1; i++) {
	if (src[i] == '.') {
	    while(j <= FAT_FILENAME_LENGTH - 4) {
		dst[j] = ' ';
		j++;
	    }
	} else {
	    dst[j] = toupper(src[i]);
	    j++;
	}
    }
    
    while(j <= FAT_FILENAME_LENGTH - 1) {
	dst[j] = ' ';
	j++;
    }

    dst[FAT_FILENAME_LENGTH] = '\0';
}

void fat2filename(unsigned char *dst, const unsigned char *src) {
    int i, j;

    for (i = 0; i <= strlen(src); i++) {
	if (src[i] == ' ' || src[i] == '\0' || i >= FAT_FILENAME_LENGTH - 3)
	    break;
	dst[i] = tolower(src[i]);
    }
    
    j = FAT_FILENAME_LENGTH - 3;

    if (src[j] != ' ' && src[j] != '\0') {
	dst[i++] = '.';
	for (; j <= strlen(src); j++) {
	    if (src[j] == ' ' || src[j] == '\0' || j >= FAT_FILENAME_LENGTH)
		break;
	    dst[i++] = tolower(src[j]);
	}
    } 
    dst[i] = '\0';
}

int file_exists_in_image(const unsigned char *fname, struct boot_floppy_t *boot_floppy) {
    int i;
    int found = 0;
    unsigned char fatname[FAT_FILENAME_LENGTH + 2];

    filename2fat(fatname, fname);

    for (i = 0; i <= boot_floppy->fat_root_dir_size - 1; i++) {
	if (strncmp(boot_floppy->fat_root_dir[i].name, fatname, FAT_FILENAME_LENGTH) == 0) {
	    found = 1;
	    break;
	}
    }
    
    return found;
}

void show_root_dir(struct boot_floppy_t *boot_floppy) {
    int i;

    for (i = 0; i <= boot_floppy->fat_root_dir_size - 1; i++) {
	if (opt_verbose) printf("File: '%s' Pos: %8d, Size: %8d\n", boot_floppy->fat_root_dir[i].name,
	                                           boot_floppy->fat_root_dir[i].pos,
	                                           boot_floppy->fat_root_dir[i].size);
    }
}

void list_files(struct boot_floppy_t *boot_floppy) {
    int i;
    unsigned char fname[FAT_FILENAME_LENGTH + 2];

    for (i = 0; i <= boot_floppy->fat_root_dir_size - 1; i++) {
	fat2filename(fname, boot_floppy->fat_root_dir[i].name);
	printf("%s\n", fname);
    }
}

struct dir_entry_t get_dir_entry(const unsigned char *fname, struct boot_floppy_t *boot_floppy) {
    unsigned char fat_name[FAT_FILENAME_LENGTH + 2];
    int i;
    struct dir_entry_t dir_entry;

    filename2fat(fat_name, fname);

    for (i = 0; i <= boot_floppy->fat_root_dir_size - 1; i++) {
	if (strncmp(boot_floppy->fat_root_dir[i].name, fat_name, FAT_FILENAME_LENGTH) == 0) {
	    strncpy(dir_entry.name, boot_floppy->fat_root_dir[i].name, FAT_FILENAME_LENGTH);
	    dir_entry.pos  = boot_floppy->fat_root_dir[i].pos;
	    dir_entry.size = boot_floppy->fat_root_dir[i].size;
	    break;
	}
    }

    if (opt_verbose) printf("File: [%s], Pos: %d, Size: %d\n", fname, dir_entry.pos, dir_entry.size);

    return dir_entry;
}

int read_file(FILE *fh, struct boot_floppy_t *boot_floppy, struct dir_entry_t dir_entry, unsigned char *buffer) {
    int i;

    // seek to file
    if (fseek(fh,
	      (long) (boot_floppy->boot_img_offset + (dir_entry.pos + boot_floppy->FirstRootDirSecNum - 1) * boot_floppy->BytesPerCluster),
	      SEEK_SET) != 0) {
        fprintf(stderr, "Error seeking into file\n");
        return 0;
    }
    
    // read file into buffer
    if (fread(((void *) buffer),
              (size_t) dir_entry.size,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error reading file\n");
        return 0;
    }

    return 1;
}

int write_file(FILE *fh, struct boot_floppy_t *boot_floppy, struct dir_entry_t dir_entry, unsigned char *buffer) {
    int i;

    // seek to file
    if (fseek(fh,
	      (long) (boot_floppy->boot_img_offset + (dir_entry.pos + boot_floppy->FirstRootDirSecNum - 1) * boot_floppy->BytesPerCluster),
	      SEEK_SET) != 0) {
        fprintf(stderr, "Error seeking into file\n");
        return 0;
    }
    
    // read file into buffer
    if (fwrite(((void *) buffer),
              (size_t) dir_entry.size,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error writing file\n");
        return 0;
    }

    return 1;
}

void show_buffer(unsigned char *buffer, long size) {
    int i;

    if (opt_verbose) printf("File-Contents:\n");
    if (opt_verbose) printf("---------------->\n");
    for (i = 0; i <= size - 1; i++)
	if (opt_verbose) printf("%c", buffer[i]);
    if (opt_verbose) printf("<----------------\n");
}

int output_buffer(FILE *fh, unsigned char *buffer, long size) {
    if (fwrite(((void *) buffer),
	       (size_t) size,
	       (size_t) 1,
	       fh) < 1) {
        fprintf(stderr, "Error outputting file\n");
    }

    return 1;
}

int export_to_local_file (FILE *fh, struct boot_floppy_t *boot_floppy, const unsigned char *image_file, const unsigned char *local_file) {
    FILE *fh2;
    unsigned char *buffer;
    struct dir_entry_t dir_entry;

    dir_entry = get_dir_entry(image_file, boot_floppy);
    
    // allocate read_buffer for reading file
    if ((buffer = malloc((size_t) (dir_entry.size))) == NULL) {
	fprintf(stderr, "Error mallocing buffer for reading file\n");
	return 0;
    }
    
    if (read_file(fh, boot_floppy, dir_entry, buffer) == 0) {
	fprintf(stderr, "Error reading file. Aborting\n");
	return 0;
    }
    
    if (strncmp(local_file, "-", 1) == 0)
	fh2 = stdout;
    else
	if ((fh2 = fopen(local_file, "w")) == NULL) {
	    fprintf(stderr, "Error writing file '%s'\n", local_file);
	    return 0;
	}
    
    if(output_buffer(fh2, buffer, dir_entry.size) == 0) {
	fprintf(stderr, "Error outputting buffer. Aborting\n");
	fclose(fh2);
	return 0;
    }
    
    fclose(fh2);
    free(buffer);

    return 1;
}

int import_from_local_file(FILE *fh, struct boot_floppy_t *boot_floppy, const unsigned char *image_file, const unsigned char *local_file) {
    FILE *fh2;
    unsigned char *buffer;
    struct dir_entry_t dir_entry;

    dir_entry = get_dir_entry(image_file, boot_floppy);
    
    // allocate read_buffer for reading file
    if ((buffer = malloc((size_t) (dir_entry.size))) == NULL) {
	fprintf(stderr, "Error mallocing buffer for reading file\n");
	return 0;
    }

    if (strncmp(local_file, "-", 1) == 0)
	fh2 = stdin;
    else
	if ((fh2 = fopen(local_file, "r")) == NULL) {
	    fprintf(stderr, "Error reading file '%s'\n", local_file);
	    return 0;
	}
    
    // read local file into buffer
    if (fread(((void *) buffer),
              (size_t) dir_entry.size,
              (size_t) 1,
              fh2) < 1) {
        fprintf(stderr, "Error reading local file\n");
        return 0;
    }

    if (write_file(fh, boot_floppy, dir_entry, buffer) == 0) {
	fprintf(stderr, "Error writing file. Aborting\n");
	return 0;
    }

    fclose(fh2);
    free(buffer);

    return 1;
}

int import_floppy(FILE *fh, struct boot_floppy_t *boot_floppy, const unsigned char *local_file) {
    FILE *fh2;
    unsigned char *buffer;

    // allocate read_buffer for reading file
    if ((buffer = malloc((size_t) (boot_floppy->boot_img_size))) == NULL) {
	fprintf(stderr, "Error mallocing buffer for reading file\n");
	return 0;
    }

    if (strncmp(local_file, "-", 1) == 0)
	fh2 = stdin;
    else
	if ((fh2 = fopen(local_file, "r")) == NULL) {
	    fprintf(stderr, "Error reading file '%s'\n", local_file);
	    return 0;
	}
    
    // read local file into buffer
    if (fread(((void *) buffer),
              (size_t) boot_floppy->boot_img_size,
              (size_t) 1,
              fh2) < 1) {
        fprintf(stderr, "Error reading local file\n");
        return 0;
    }

    // seek to boot image
    fseek(fh, boot_floppy->boot_img_offset, SEEK_SET);

    if (fwrite(((void *) buffer),
	       (size_t) boot_floppy->boot_img_size,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error writing to image\n");
        return 0;
    }

    fclose(fh2);
    free(buffer);

    return 1;
}

int export_floppy(FILE *fh, struct boot_floppy_t *boot_floppy, const unsigned char *local_file) {
    FILE *fh2;
    unsigned char *buffer;
    
    // allocate read_buffer for reading file
    if ((buffer = malloc((size_t) (boot_floppy->boot_img_size))) == NULL) {
	fprintf(stderr, "Error mallocing buffer for reading file\n");
	return 0;
    }

    // seek to boot image
    fseek(fh, boot_floppy->boot_img_offset, SEEK_SET);

    // read local file into buffer
    if (fread(((void *) buffer),
              (size_t)  boot_floppy->boot_img_size,
              (size_t) 1,
              fh) < 1) {
        fprintf(stderr, "Error reading boot floppy from image\n");
        return 0;
    }
    
    if (strncmp(local_file, "-", 1) == 0)
	fh2 = stdout;
    else
	if ((fh2 = fopen(local_file, "w")) == NULL) {
	    fprintf(stderr, "Error writing file '%s'\n", local_file);
	    return 0;
	}
    
    if(output_buffer(fh2, buffer, boot_floppy->boot_img_size) == 0) {
	fprintf(stderr, "Error outputting buffer. Aborting\n");
	fclose(fh2);
	return 0;
    }
    
    fclose(fh2);
    free(buffer);

    return 1;
}



void show_help(char *name) {
    fprintf(stderr, "%s %s\n  written by %s\n\n", KNOPPIX_CUSTOMIZE_NAME,
                                                  KNOPPIX_CUSTOMIZE_VERSION,
                                                  KNOPPIX_CUSTOMIZE_AUTHOR);
    fprintf(stderr, "Usage: %s [OPTION]\n", name);
    fprintf(stderr, "      --action ACTION            action to perform. Mandatory!\n"); 
    fprintf(stderr, "               list              list files in boot image\n");
    fprintf(stderr, "               export_file       export file from image to localfile\n");
    fprintf(stderr, "               import_file       import local file into image\n");
    fprintf(stderr, "               export_floppy     export boot floppy from image to localfile\n");
    fprintf(stderr, "               import_floppy     import boot floppy from local file into image\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "      --image KNOPPIX-Image-File the Knoppix image to customize. Mandatory!\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "      --local_file filename      filepath on local filesystem.\n");
    fprintf(stderr, "                                 Mandatory with --action export_* and import_*\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "      --image_file filename      file in the image (maybe . when importing)'\n");
    fprintf(stderr, "                                 Mandatory with --action export_file and import_file\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "      --verbose                  be verbose\n"); 
    fprintf(stderr, "\n");
    fprintf(stderr, "      --help                     this help\n"); 
}

int main(int argc, char **argv) {
    char knoppix_image_filename[PATH_MAX];
    struct stat stat_buf, stat_buf2;
    FILE *fh, *fh2;
    struct boot_floppy_t boot_floppy;
    int c;

    int opt_help          = 0;
    int opt_local_file    = 0;
    int opt_image_file    = 0;
    int opt_image         = 0;
    int opt_action        = 0;
    int cmd_list          = 0;
    int cmd_export_file   = 0;
    int cmd_import_file   = 0;
    int cmd_export_floppy = 0;
    int cmd_import_floppy = 0;

    char local_filename[PATH_MAX];
    char image_filename[FAT_FILENAME_LENGTH + 2];

    while (1) {
	int this_option_optind = optind ? optind : 1;
	int option_index = 0;
	static struct option long_options[] = {
	    {"help",        0, 0, 0},
	    {"verbose",     0, 0, 0},
	    {"action",      1, 0, 0},
	    {"local_file",  1, 0, 0},
	    {"image_file",  1, 0, 0},
	    {"image",       1, 0, 0},
	    {0,             0, 0, 0}
	};
	
	c = getopt_long (argc, argv, "", long_options, &option_index);
	if (c == -1) 
	    break;
	
	if (c == 0) {
	    if (strncmp(long_options[option_index].name, "help", 4) == 0) {
		opt_help = 1;
		continue;
	    }

	    if (strncmp(long_options[option_index].name, "verbose", 7) == 0) {
		opt_verbose = 1;
		continue;
	    }

	    if (strncmp(long_options[option_index].name, "local_file", 10) == 0) {
		opt_local_file = 1;
		strncpy(local_filename, optarg, PATH_MAX);
		continue;
	    }

	    if (strncmp(long_options[option_index].name, "image_file", 10) == 0) {
		opt_image_file = 1;
		strncpy(image_filename, optarg, FAT_FILENAME_LENGTH + 1);
		continue;
	    }

	    if (strncmp(long_options[option_index].name, "image", 5) == 0) {
		opt_image = 1;
		strncpy(knoppix_image_filename, optarg, PATH_MAX);
		continue;
	    }

	    if (strncmp(long_options[option_index].name, "action", 6) == 0) {
		opt_action = 1;
		
		if (strncmp(optarg, "list", 4) == 0)
		    cmd_list = 1;
		else if (strncmp(optarg, "export_file", 10) == 0)
		    cmd_export_file = 1;
		else if (strncmp(optarg, "import_file", 10) == 0)
		    cmd_import_file = 1;
		else if (strncmp(optarg, "import_floppy", 12) == 0)
		    cmd_import_floppy = 1;
		else if (strncmp(optarg, "export_floppy", 12) == 0)
		    cmd_export_floppy = 1;

		
		continue;
	    }
	} 
    }

    // there should be no further arguments
    if (optind < argc) {
	fprintf(stderr, "Usage: %s [OPTION]\n", argv[0]);
	fprintf(stderr, "      Unknown arguments: ");
	while (optind < argc)
                   fprintf(stderr, "%s ", argv[optind++]);
	fprintf(stderr, "\n");
	fprintf(stderr, "      try --help for more information\n");

	exit(1);
    }

    // show help
    if (opt_help) {
	show_help(basename(argv[0]));
	exit(0);
    }

    // --image and --action are mandatory
    if (! opt_image || ! opt_action) {
	fprintf(stderr, "Usage: %s [OPTION]\n", argv[0]);
	fprintf(stderr, "      --image and --action are mandatory\n");
	fprintf(stderr, "      try --help for more information\n");
	exit(1);
    }

    // --local_file and image_file are mandatory for --export_file and --import_file
    if ((cmd_export_file || cmd_import_file) && ! (opt_local_file && opt_image_file)) {
	fprintf(stderr, "Usage: %s [OPTION]\n", argv[0]);
	fprintf(stderr, "      --local_file and --image_file are mandatory with\n");
	fprintf(stderr, "      --action export_file and --action import_file\n");
	fprintf(stderr, "      try --help for more information\n");
	exit(1);
    }

    // --local_file is mandatory for --export_floppy and --import_floppy
    if ((cmd_export_floppy || cmd_import_floppy) && ! opt_local_file) {
	fprintf(stderr, "Usage: %s [OPTION]\n", argv[0]);
	fprintf(stderr, "      --local_file is mandatory with\n");
	fprintf(stderr, "      --action export_floppy and --action import_floppy\n");
	fprintf(stderr, "      try --help for more information\n");
	exit(1);
    }

    // test if image exists
    if (stat(knoppix_image_filename, &stat_buf) != 0) {
        fprintf(stderr, "Error: Could not stat image file '%s'\n", knoppix_image_filename);
        exit(2);
    }

    // test if local_file exists
    if (cmd_import_file || cmd_import_floppy) {
	if (!strncmp(local_filename, "-", 1) == 0)
	    if (stat(local_filename, &stat_buf2) != 0) {
		fprintf(stderr, "Error: Could not stat local file '%s'\n", local_filename);
		exit(2);
	    }
    }

    // open the image file for reading and writing
    if (cmd_import_file || cmd_import_floppy) {
	if ((fh = fopen(knoppix_image_filename, "r+")) == NULL) {
	    fprintf(stderr, "Error opening image '%s' for read/write\n", knoppix_image_filename);
	    exit(2);
	}
    } else {
	if ((fh = fopen(knoppix_image_filename, "r")) == NULL) {
	    fprintf(stderr, "Error opening image '%s' for read\n", knoppix_image_filename);
	    exit(2);
	}
    }

    if (opt_verbose) printf("%s %s\n", KNOPPIX_CUSTOMIZE_NAME, KNOPPIX_CUSTOMIZE_VERSION);

    // find out if it's ISO or Floppy-image, in ISO find the floppy image
    if (find_boot_image(fh, stat_buf, &boot_floppy) == 0) {
        fprintf(stderr, "Error reading image. Aborting\n");
        fclose(fh);
        exit(3);
    }

    // Get the boot floppy's root directory
    if (get_fat_root_dir(fh, &boot_floppy) == 0) {
        fprintf(stderr, "Error parsing boot image. Aborting\n");
        fclose(fh);
        exit(3);
    }

    if (opt_verbose)
	show_root_dir(&boot_floppy);

    // test if image_file exists
    // can only test this after reading the image

    // if image-file is "." use file name from local_filename (without directory)
    if (cmd_import_file && (strncmp(image_filename, ".", 1) == 0)) {
	strncpy(image_filename, basename(local_filename), FAT_FILENAME_LENGTH + 1);
    }

    if ((cmd_import_file || cmd_export_file) && !file_exists_in_image(image_filename, &boot_floppy)) {
        fprintf(stderr, "Error: image_file '%s' not in image\n", image_filename);
        exit(2);
    }

    // list files on boot floppy
    if (cmd_list)
	list_files(&boot_floppy);

    // export file from image to filesystem
    if (cmd_export_file) {
	if (!strncmp(local_filename, "-", 1) == 0)
	    if (stat(local_filename, &stat_buf2) == 0) {
		if (S_ISDIR(stat_buf2.st_mode)) {
		    if (local_filename[strlen(local_filename)-1] != G_DIR_SEPARATOR)
			strncat(local_filename, G_DIR_SEPARATOR_S, PATH_MAX);
		    strncat(local_filename, image_filename, PATH_MAX);
		}
	    } 
	
	if(export_to_local_file(fh, &boot_floppy, image_filename, local_filename) == 0) {
	    fprintf(stderr, "Error exporting file to local file. Aborting\n");
	    fclose(fh);
	    exit(3);
	}
    }
	
    // import file from filesystem into image
    if (cmd_import_file)
	if(import_from_local_file(fh, &boot_floppy, image_filename, local_filename) == 0) {
	    fprintf(stderr, "Error importing file from local file and writing to image. Aborting\n"); 
	    fclose(fh);
	    exit(3);
	}

    // export boot floppy from image to filesystem
    if (cmd_export_floppy)
	if(export_floppy(fh, &boot_floppy, local_filename) == 0) {
	    fprintf(stderr, "Error exporting boot floppy to local file. Aborting\n"); 
	    fclose(fh);
	    exit(3);
	}

    // import boot floppy from filesystem into image
    if (cmd_import_floppy)
	if(import_floppy(fh, &boot_floppy, local_filename) == 0) {
	    fprintf(stderr, "Error importing boot floppy and writing to image. Aborting\n"); 
	    fclose(fh);
	    exit(3);
	}

    
    fclose(fh);

    exit(0);
}

