/////////////////////////////////////////////////////////////////////////-*-C-*- 
//
// Copyright (c) 2002, 2003 Xilinx, Inc.  All rights reserved.
//
// Xilinx, Inc.
//
// XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS" AS A 
// COURTESY TO YOU.  BY PROVIDING THIS DESIGN, CODE, OR INFORMATION AS
// ONE POSSIBLE   IMPLEMENTATION OF THIS FEATURE, APPLICATION OR 
// STANDARD, XILINX IS MAKING NO REPRESENTATION THAT THIS IMPLEMENTATION
// IS FREE FROM ANY CLAIMS OF INFRINGEMENT, AND YOU ARE RESPONSIBLE 
// FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE FOR YOUR IMPLEMENTATION.  
// XILINX EXPRESSLY DISCLAIMS ANY WARRANTY WHATSOEVER WITH RESPECT TO 
// THE ADEQUACY OF THE IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO 
// ANY WARRANTIES OR REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE 
// FROM CLAIMS OF INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE.
// 
// File   : mfs_gen.c
//
// Description : 
// Utility program to create memory file system image on a host machine
// Can be used to pre-load a file system on flash using XMD or other program to
// write the memory image to flash.
//
// This program has been compiled and tested using gcc under Cygwin and Solaris
//          gcc mfsgen.c mfs_filesys.c mfs_filesys_util.c -o mfsgen
//
// $Id: mfsgen.c,v 1.1 2003/01/10 19:15:09 mohan Exp $
//
////////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "xilmfs.h"
/* number of blocks to allocate for file system */
#define NUM_BLOCKS 200

static int process_file (char *filename) ;
static int process_dir(char *dirname) ;
static int mfs_switch_endian_char(struct mfs_file_block *block, char *resultarr) ;
static int mfs_extract() ;

static int print_usage_and_exit()
{
  printf("Usage: mfsgen -txcvsf [mfs_filename] <filelist>\n");    
  printf("\tSpecify exactly one of c,t,x modes\n");
  printf("\t t: list the files in the mfs file system image\n");
  printf("\t c: create an mfs file system image using the list of files specified on the command line\n");
  printf("\t Directories specified in this list are traversed recursively\n");
  printf("\t x: extract the mfs file system from image to host file system\n");
  printf("\t v: verbose mode\n");
  printf("\t f: specify the host file name (mfs_filename) where  the mfs file system image is stored\n");
  printf("\t If the f option is specified the mfs filename should be specified\n");
  printf("\t If the f option is omitted the default file name is filesystem.mfs\n");
  printf("\t s: switch endianness\n");
  exit(1);
}
 
int main(int argc, char *argv[])
{
  int create_mode = 0;
  int read_mode = 0;
  int extract_mode = 0;
  int verbose_mode = 0;
  int mfs_filename_specified = 0;
  int switch_endian_mode = 0;
  char *mfs_filename;

  int index;
  int foutput; 
  int numblocks_used = 0;
  int numblocks_free = 0;
  if (argc < 3)
    print_usage_and_exit();

 
  if (argv[1][0] == '-') {
    // process options
    index = 1;
    while (argv[1][index] != '\0') {
      if (argv[1][index] == 't') {
        read_mode = 1;
      }
      else if (argv[1][index] == 'x') {
        extract_mode = 1;
      }
      else if (argv[1][index] == 'c') {
        create_mode = 1;
      }
      else if (argv[1][index] == 'v') {
        verbose_mode = 1;
      }
      else if (argv[1][index] == 'f') {
        mfs_filename_specified = 1;
      }
      else if (argv[1][index] == 's') {
        switch_endian_mode = 1;
      }
      else {
        print_usage_and_exit();
      }
      index++;
    }
  }
  else
    print_usage_and_exit();
  if (read_mode + extract_mode + create_mode > 1) {
    print_usage_and_exit();
  }
  if (mfs_filename_specified) {
    if (create_mode && argc < 4) { /* filelist can be omitted for read or extract modes - it is ignored anyway */
      print_usage_and_exit();
    }
    mfs_filename = argv[2];
    index = 3; /* index of first item in filelist in argv */
  } 
  else {
    index = 2; /* index of first item in filelist in argv */
    mfs_filename = "filesystem.mfs" ;
  }
  if (create_mode) {
    int numbytes;
    char *address;
    int block_length = sizeof(struct mfs_file_block);
    
    numbytes = NUM_BLOCKS * block_length;
    address = (char *)malloc(numbytes);
    // Initialize the MFS file_system
    mfs_init_fs(numbytes, address, MFSINIT_NEW);  
    
    // process all the host files
    while (index < argc) {
      process_file(argv[index]);
      index++;
    }
    
    // Now that all files are read, dump the memory onto a file.
    if((foutput= open (mfs_filename, O_WRONLY | O_CREAT, 0666)) < 0) {
      printf("Cannot open file %s for writing: check permissions\n", mfs_filename);
      exit(1);
    }
    if (switch_endian_mode) {
      struct mfs_file_block *block_array;
      int block_num;
      char *tmpchararray;
      block_array = (struct mfs_file_block *)address;
      tmpchararray = (char *)malloc(block_length);
      for (block_num = 0; block_num < NUM_BLOCKS; block_num++) {
        mfs_switch_endian_char(&(block_array[block_num]), tmpchararray);
        write(foutput, tmpchararray, block_length);
      }
    }
    else {
      write(foutput, address, numbytes);
    }
    close(foutput);
    mfs_get_usage(&numblocks_used, &numblocks_free);
    printf("MFS block usage (used / free / total) = %d / %d / %d\n", numblocks_used, numblocks_free, NUM_BLOCKS);
    printf("Size of memory is %d bytes\n",numbytes);
    printf("Block size is %d\n", sizeof(struct mfs_file_block));
  }
  else { // read_mode or extract_mode
    int finput; // file descriptor for mfs_file
    int numbytes;
    int numblocks=0; // number of blocks in MFS file
    char *address; // for mfs file system
    finput = open(mfs_filename, O_RDONLY);
    if (finput < 0) {
      printf("Unable to open mfs_file %s\n", mfs_filename);
    }
    numbytes = lseek(finput,0, SEEK_END);
    if (numbytes >= 0) {
      numblocks = numbytes/(sizeof(struct mfs_file_block)); 
    }
    if (numblocks <=0) {
      printf("Empty mfs file system\n");
      exit(1);
    }
    address = (char *)malloc(numbytes);
    lseek(finput,0,SEEK_SET);
    if (read(finput,address,numbytes) != numbytes) {
      printf("error reading mfs file\n");
      exit(1);
    }
    mfs_init_fs(numbytes, address, MFSINIT_ROM_IMAGE);
    // now walk through the mfs file system and either list or extract
    if(read_mode)
      mfs_ls_r();
    else // extract_mode
      mfs_extract();
  }
  return 0;
}


static int process_file (char *filename) {
  struct stat statbufs;
  struct stat *statbuf = &statbufs;
  if (stat(filename, statbuf)== -1) {
    printf("cannot stat %s\n", filename);
  }
  else if (strlen(filename) >= MFS_MAX_FILENAME_LENGTH-1 ) {
    printf("Name too long: %s\n", filename);
  }
  else {
    if (S_ISDIR(statbuf->st_mode)) {
      if ((strcmp(filename, "..") == 0) || (strcmp(filename,".") == 0)) {
        // printf("Not processing directory %s\n", filename);
      }
      else {
        printf("%s:\n", filename);
        process_dir(filename);
      }
    }
    if (S_ISREG(statbuf->st_mode)) {
      int f_host;
      int f_mfs;   
      int numbytes; 
      int total_bytes = 0;  
      char line[256];
      printf("%s ", filename);
      // Create the file on MFS 
      if ((f_mfs = mfs_file_open(filename,MFS_MODE_CREATE)) == -1 ) {
	printf("Error: Could not open file %s on MFS \n",filename);
        exit(1);
      }
      
      // Open the file on the host
      if ((f_host=open(filename,O_RDONLY)) == -1) {
	printf("Error: Could not open fle %s on host \n",filename);
	exit(1);
      }

      //Read each line on the host and write it to MFS
      while((numbytes=read(f_host, line,256)) > 0){
	if (mfs_file_write(f_mfs,line,numbytes)==0) {
          printf("error writing mfs file\n");
          exit(1);
        }
        total_bytes += numbytes;
      }
      printf(" %d\n", total_bytes);
      // Close both HOST and MFS file
      close(f_host);
      mfs_file_close(f_mfs);
    }
  } 
}

static int process_dir(char *dirname) {
  DIR *dirp;
  int errno;
  struct dirent *dp;
  struct stat statbufs;
  struct stat *statbuf = &statbufs;
  dirp = opendir(dirname);
  if (dirp) {
    errno = 0;
    // printf("Creating directory %s\n", dirname);
    if (mfs_create_dir(dirname) <= 0) {
      printf("cannot create MFS dir %s\n", dirname);
      exit(1);
    }
    mfs_change_dir(dirname);
    chdir(dirname);
    while ((dp = readdir(dirp)) != NULL) {
      // printf("entry: %s\n", dp->d_name);
      if (stat(dp->d_name, statbuf) == -1) {
        printf("cannot stat %s\n", dp->d_name);
      }
      else {
        if (S_ISDIR(statbuf->st_mode)) {
          // printf("%s is a dir\n", dp->d_name);
        }
        if (S_ISREG(statbuf->st_mode)) {
          // printf("%s is a regular file\n", dp->d_name);
        }
        if ((strcmp(dp->d_name,"..") == 0) || (strcmp(dp->d_name, ".") == 0)) {
          // printf("Ignoring directory %s\n", dp->d_name);
        }
        else
           process_file(dp->d_name);
      }
    }
    chdir("..");
    mfs_change_dir("..");
    closedir(dirp);
    if (errno == 0) {
      return 1;
    }
    else{
      return 0;
    }
  }

}

static int mfs_extract() {
  int fd = mfs_dir_open(".");
  int entry_type;
  int entry_size;
  char *entry_name;
  while (mfs_dir_read(fd, &entry_name, &entry_size, &entry_type) != 0) {
    if (entry_type == MFS_BLOCK_TYPE_DIR) {
      if (strcmp(entry_name, ".") && strcmp(entry_name, "..")) {
	if (!mfs_change_dir(entry_name))
          printf("Failed\n");
        else {
          printf("cd %s\n",entry_name);
          mkdir(entry_name, 0777);
          chdir(entry_name);
          mfs_extract();
          if(mfs_change_dir("..")){
            printf("cd..\n");
            chdir("..");
          }
          else {
            printf("Failed..\n");
            exit(1);
          }
        }
      }
    }
    else {
      int host_fd;
      int mfs_fd;
      char data[MFS_BLOCK_DATA_SIZE]; /* read one block at a time */
      int num_read;
      printf("%s\n",entry_name);
      if ((host_fd = open(entry_name, O_WRONLY | O_CREAT, 0666)) < 0) {
        printf("cannot open file %s on host\n", entry_name);
      }
      else {
        if ((mfs_fd = mfs_file_open(entry_name, MFS_MODE_READ)) < 0) {
          printf("Cannot open mfs file for reading\n");
          exit(1);
        }
        while ((num_read = mfs_file_read(mfs_fd, data, MFS_BLOCK_DATA_SIZE)) > 0) {
          write(host_fd, data, num_read);
        }
        mfs_file_close(mfs_fd);
        close(host_fd);
      }

    }
  }
  mfs_dir_close(fd);
}

/**
 * Integers and shorts are stored in different formats on different machines.
 * An integer represented by the hex value 0xffeeddcc
 * is stored in bytes n+0, n+1, n+2, n+3 as follows:
 * On Solaris, MicroBlaze
 * n+0 : ff
 * n+1 : ee
 * n+2 : dd
 * n+3 : cc
 * On PCs
 * n+0 : cc
 * n+1 : dd
 * n+2 : ee
 * n+3 : ff
 * Similarly a short integer represented by the hex value 0xffee
 * is stored in bytes n+0, n+1 as follows:
 * On Solaris, MicroBlaze
 * n+0 : ff
 * n+1 : ee
 * On PCs
 * n+0 : ee
 * n+1 : ff
 * This function takes in a basic file system block and copies it to
 * a new array resultarr, with the bytes reordered for all int or short members
 * of the mfs_file_block struct and its member structures
 * This function is called whenever an mfs_file_block structure is created to
 * be read on a machine with the other byte ordering convention
 * WARNING: Any changes to the mfs_file_block structure, including changing the 
 * order of members, addition or deletion of members or modification of 
 * member structures, should be reflected in this function
 */
static int mfs_switch_endian_char(struct mfs_file_block *block, char *resultarr) {
  int block_length = sizeof(struct mfs_file_block);
  int index = 0;
  char *input;
  /* go through all the elements of mfs_file_block in order and copy them from block to resultarr, switching endianness where necessary */
  input = (char *)block;
  /* short block_size */
  resultarr[index] = input[index+1];
  resultarr[index+1] = input[index];
  index += 2;
  /* short block_type */
  resultarr[index] = input[index+1];
  resultarr[index+1] = input[index];
  index += 2;
  /* int next_block */
  resultarr[index+3] = input[index];
  resultarr[index+2] = input[index+1];
  resultarr[index+1] = input[index+2];
  resultarr[index] = input[index+3];
  index += 4;
  /* int prev_block */
  resultarr[index+3] = input[index];
  resultarr[index+2] = input[index+1];
  resultarr[index+1] = input[index+2];
  resultarr[index] = input[index+3];
  index += 4;
  /* int index */
  resultarr[index+3] = input[index];
  resultarr[index+2] = input[index+1];
  resultarr[index+1] = input[index+2];
  resultarr[index] = input[index+3];
  index += 4;
  /* the union block_data or dir_data */
  if (block->block_type == MFS_BLOCK_TYPE_DIR) {
    int dirent;
    /* struct mfs_dir_block */
    /* short num_entries */
    resultarr[index] = input[index+1];
    resultarr[index+1] = input[index];
    index += 2;
    /* short num_deleted */  
    resultarr[index] = input[index+1];
    resultarr[index+1] = input[index];
    index += 2;
    /* struct mfs_dir_ent_block[MFS_MAX_LOCAL_ENT] */
    for (dirent = 0; dirent < MFS_MAX_LOCAL_ENT; dirent++) {
      /* struct mfs_dir_ent_block */
      /* char name[MFS_MAX_FILENAME_LENGTH] */
      int nameindex;
      for (nameindex = 0; nameindex < MFS_MAX_FILENAME_LENGTH; nameindex++){
        resultarr[index] = input[index];
        index++;
      }
      /* char deleted */
      resultarr[index] = input[index];
      index++;
      /* unsigned int index */  
      resultarr[index+3] = input[index];
      resultarr[index+2] = input[index+1];
      resultarr[index+1] = input[index+2];
      resultarr[index] = input[index+3];
      index += 4;
    }
    /* rest of the bytes, if any */
    while (index < block_length) { 
      resultarr[index] = input[index];
      index++;
    }
  }
  else { /* block_type = MFS_BLOCK_TYPE_FILE or other -> treat it as a file block */
    /* char block_data[] */
    while (index < block_length) { 
      resultarr[index] = input[index];
      index++;
    }
  }
  return 1;
}

