/* LICENSE:
  =========================================================================
    CMPack'04 Source Code Release for OPEN-R SDK 1.1.5-r2 for ERS7
    Copyright (C) 2004 Multirobot Lab [Project Head: Manuela Veloso]
    School of Computer Science, Carnegie Mellon University
    All rights reserved.
  ========================================================================= */

/*
  Note on program: 

  This was developed concurrently with a prototype of the EasyNet networking
  framework on which the server object is based. To minimize the transmission
  problems that that early version was having, I started developing this program
  with a sweeping approach whereby the whole directory tree is requested from
  the Aibo in a single go, reducing the interactivity required. From the returned
  listing, a hierarchical linked list is then set up to aid in the detection of
  which files have to be transferred or deleted.

  If I were to re-implement this now, I would probably try a purely recursive
  approach, doing away with all the linked list stuff...  Oh well, the program's
  here now-- if you want to have fun...  :)

*/

const char VERSION[] = "v1.00a_r1";

#include <arpa/inet.h>
#include <dirent.h>     // for 'dirent' structure to get dir entry names and size
#include <errno.h>      // for 'errno', to use 'perror' function
#include <fcntl.h>      // for 'fcntl', to make socket non-blocking
#include <netdb.h>      // for 'hostent' structure, usesed for address lookup
#include <netinet/in.h>
#include <signal.h>     // for 'SIGINT', to intercept Ctrl-C
#include <stdbool.h>    // for 'bool', 'true', 'false'
#include <stdio.h>      // for 'stderr', in sigint_handler(), and 'remove'
#include <stdlib.h>
#include <string.h>     // for 'strlen'
#include <sys/socket.h>
#include <sys/stat.h>   // for 'stat' function, to get file attributes
#include <sys/time.h>   // for 'gettimeofday' (#includes time.h)
#include <unistd.h>     // for 'unlink', 'rmdir' etc


// Definitions for network communication
#define ERR_NONE     0
#define ERR_TIMEOUT -1
#define ERR_LOOKUP  -2
#define ERR_CONNECT -3
#define ERR_OTHER   -4

// Max command-line tokens
#define MAXTOKENS  7

// This will be printed in case of error: "Error xxx: "
char errcause[][30] = {" reporting error", // 0 is no error
                       " connecting",
                       " resolving address",
                       " connecting",
                       "",
};

// Command-specific error strings
char TimedOutStr[]    =  "Operation timed out";
char ListErrStr[][50] = {"Server replied: No such file or directory",
                         "Could not send request",
                         "Invalid response received from host",
                         "Invalid filename received from host",
};
char GetErrStr[][50]  = {"Server replied: Invalid filename",
                         "Could not create file locally",
                         "Listing request failed",
                         "Could not send request",
                         "Invalid response received from host",
                         "Server replied: Invalid Get request",
                         "Invalid filesize received from host",
                         "Filesizes returned by List and Get do not match",
                         "Data transfer failed",
                         "Received more bytes than expected",
};

char PutErrStr[][50]  = {"Server Replied: Error creating file",
                         "Invalid filename",
                         "Could not open file for reading",
                         "Could not send request",
                         "Invalid response received from host",
                         "Data transfer failed",
                         "Could not read acknowledgement from host",
                         "Server replied: Error receiving data",
};

 
// Directory Management Linked List definitions
// The 'RelativeName' always gives a complete usable path by concatenating it
// to the 'BasePath', and -excpet for the BaseEntry- will always start with a '/'

typedef struct ListEntryStruct {
  char   Type;                       // This will be '-' if unknown or not found
  unsigned long Size;                // This will be 0 for directories
  char*  RelativeName;               // This will be an empty string for the BaseEntry
  struct ListEntryStruct *Next;      // Next entry in this same directory
  struct ListEntryStruct *Parent;    // Always a dir, will be NULL for the BaseEntry
  struct ListEntryStruct *Contents;  // This is only meaningful for directories
  struct ListEntryStruct *LastOfDir; // Only for dirs, it's the last entry in this dir
} *ListEntry;


typedef struct ListingBaseStruct {
  struct ListEntryStruct BaseEntry;  // This is the root path we were actually given
  bool       Remote;                 // Is this listing remote?
  char*      BasePath;
  int        MaxDepth;               // 0 means no subdirectories
  ListEntry  LastEntryAdded;
} *ListingBase;


// Global vars for network communication
int sock = -1;
int fd = -1;
int handle = -1;
const int BUFFERSIZE = 8192;

// Global vars for lolcal operations
char line[256];                  // Current line
char Tokens[MAXTOKENS][256];      // Holds tokens of a command
char TargetHost[100] = "";       // Name or IP of target robot
char CurrentSection[100] = "";   // Hold the name of the [Section] we're reading
char OptionsString[100]  = "";   // Contains optional switches for commands. 
char DefaultOptions[100];        // Used to revert to default switches 
char FullNameBuffers[2][256];    // Static buffers for 'FullName' function 
int  FullNameIndex = 0;          // Static buffer index for 'FullName' function

const char* errstring(int cause)
{
  // Returns appropriate error string
  switch(cause){
    case ERR_LOOKUP:
      return hstrerror(h_errno);
    case ERR_TIMEOUT:
      return TimedOutStr;
    default:
      return strerror(errno); 
  }
}


void inputstr(char* dest, int maxchars, char* prompt)
{
  printf("%s", prompt);
  fgets(dest, maxchars, stdin);
  dest[strlen(dest)-1] = '\0';
}


void ExitProc(void)
{
  if(sock >= 0) close(sock);
  if(handle != -1){
    close( handle );
  }
}


void sigint_handler(void)
{
  fprintf(stderr, "sigint_handler()\n");

  if(sock >= 0) close(sock);
  if(fd >= 0) close(fd);
  exit(1);
}


// ----------------------------------------------
// ---------- Network Lookup functions ----------
// ----------------------------------------------
//
//    netaddrofname: takes a hostname string, returns an IP unsigned long
//    straddrofname: takes a hostname string, returns an IP string
//
//    The latter two functions return NULL if no address is found

unsigned long netaddrofname(char *hostname)
{
  struct hostent* host = gethostbyname(hostname);
  if(host == NULL){
    return 0;
  }else{
    return ((struct in_addr *)host->h_addr_list[0])->s_addr;
  }
}

char *straddrofname(char *hostname)
{
  struct hostent* host = gethostbyname(hostname);
  if(host == NULL){
    return NULL;
  }else{
    return (char*)inet_ntoa(*(struct in_addr *)host->h_addr_list[0]);
  }
}

// ----------------------------------------------
// ----------                          ----------
// ----------------------------------------------



// Establish a connection to the robot
void Connect(char *host, unsigned short port)
{
  struct sockaddr_in  saddr;
    
  if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    perror("socket");
    exit(1);
  }

  memset((void*)&saddr, 0, sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_port = htons(port);
  saddr.sin_addr.s_addr = netaddrofname(host);

  if(saddr.sin_addr.s_addr == 0){
    perror("lookup");
    exit(1);
  }

  if(connect(sock, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
    perror("connect");
    exit(1);
  }    
}

void Close(void)
{
  // Close the connection if one is in place
  if(sock >= 0){
    if(close(sock) < 0 ){
      perror("close");
      exit(1);
    }
    sock = -1;
  }
}


int Timeout_Connect(char *host, unsigned short port, int timeout_secs)
{
  // This routine will accept a hostname, a port, and a timeout in seconds.
  // If successful, it will return the newly created file descriptor
  // (always positive), otherwise one of the following (negative) errors:
  // ERR_TIMEOUT, ERR_LOOKUP, ERR_CONNECT, ERR_OTHER 

  struct timeval timeout;
  fd_set write_fds;
  struct sockaddr_in saddr;
  int fd;

  fd = socket(PF_INET, SOCK_STREAM, 0);

  if(fd < 0){
    return ERR_OTHER;
  }

  memset((void*)&saddr, 0, sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_port = htons(port);
  saddr.sin_addr.s_addr = netaddrofname(host);

  if(saddr.sin_addr.s_addr == 0){
    return ERR_LOOKUP;
  }

  // Set the non-blocking flag so we can use timeout
  if(fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) < 0){
    return ERR_OTHER;
  }

  // Put the specified seconds into the required time data structure
  timeout.tv_sec = timeout_secs;
  timeout.tv_usec = 0;

  while(1){
    // This loop will keep trying as long as the timer hasn't run out
    if(connect(fd,(struct sockaddr *)&saddr,
               sizeof(struct sockaddr_in)) < 0){

      if((errno != EAGAIN) && (errno != EINPROGRESS)){
        return ERR_CONNECT;
      }

    }else{
      // Connection successful, set socket to blocking
      // again and return file descriptor.

      fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & !O_NONBLOCK);
      return fd;
    }

    // Connection couldn't be established, but could be for temporary
    // resons. We will wait until the timer runs out.
    FD_ZERO(&write_fds);
    FD_SET(fd, &write_fds);

    // this is where we sneak in the timeout
    while(select(getdtablesize(), NULL, &write_fds, NULL, &timeout) < 0){
      if(errno != EINTR){
        return ERR_OTHER;
      }
    }

    if((timeout.tv_sec == 0) && (timeout.tv_usec == 0)){
      // Here the timeout has expired, otherwise the fd is now available
      // and we'll try again at the next iteration of the while loop.
      errno = ETIMEDOUT;
      return ERR_TIMEOUT;
    }		
  }

  return fd;
}


int block_read(int socket, void* dest, int min, int max)
{
  // Reads at least min bytes from socket

  int count = 0;
  char* buf;
  int bytesread = 1;
  int left;

  buf  = (char*)malloc(max);
  left = max;

  while((count < min) && (bytesread > 0)){
    bytesread = read(socket, buf + count, left);
    count += bytesread;
    if(bytesread < 0){
      return -1;  // There was an error, pass it through.
    }
    left = max - count;
  }
  memcpy(dest, buf, count);
  free(buf);
  return count;
}


inline int nread(int socket, void* dest, int min)
{
  // Reads exactly min bytes from socket
  return(block_read(socket, dest, min, min));
}


int List(char* target, unsigned char depth,
         void (*Handler)(char, unsigned long, char*, void*),
         void* HandlerParam)
{
  // This function requests a listing for the specified target and recursion
  // depth, and for each entry it calls the Handler function passed to it,
  // with a type specifier ('F' for file, 'D' for directory, '-' for end of
  // listing), the file length, the name, and as an extra parameter the list
  // to which it should be added.  Returns number of received entries, where
  // 0 means server replied that name is not valid.

  struct ListRequestStruct {
    char           CommandByte;
    unsigned char  ListingDepth;
    unsigned char  NameLength;
    char           Name[256];
  } ListRequest;

  struct ListEntryStruct {    
    unsigned long  Size;
    char           Type;
    unsigned char  NameLength;
  } ListEntry;

  char EntryName[256];
  int  b;
  int  count = 0;

  ListRequest.CommandByte  = 'l';
  ListRequest.ListingDepth = depth;
  ListRequest.NameLength   = strlen(target);
  strcpy(ListRequest.Name, target);

  if(write(sock, &ListRequest, ListRequest.NameLength + 3) < 1){
    return -1;  // Error writing request
  }

  while(1){
    b = nread(sock, &ListEntry, 6);        // Receive entry
    if(b < 0){
      return -2;  // Error receiving response
    }

    if(ListEntry.NameLength > 0){
      b = nread(sock, EntryName, ListEntry.NameLength); // Receive name
      if(b < ListEntry.NameLength){
        return -3;   // Error: not enough bytes received for name
      }
      EntryName[ListEntry.NameLength] = '\0';          // Terminate string
      if(Handler != NULL){
        (*Handler)(ListEntry.Type, ListEntry.Size, EntryName, HandlerParam);
      }
      count++;
    }else{
      return count;   // We're done, return the number of entries.
    }
  }
}


int Get(char* target, char* localname)
{
  // Get a file from the robot.

  struct GetRequestStruct {
    char           CommandByte;
    unsigned char  NameLength;
    char           Name[256];
  } Request;

  int b, RequestSize;
  unsigned long FileSize = 0, count = 0;
  FILE* file;
  char Buffer[BUFFERSIZE];

  void CloseFile(void){
    fclose(file);
    unlink(localname);
  }

  file = fopen(localname, "w");
  if(file == NULL){
    return -2;  // Error creating file locally
  }

  Request.CommandByte  = 'g';
  Request.NameLength   = strlen(target);
  strcpy(Request.Name, target);

  RequestSize = Request.NameLength + 2;
  if(write(sock, &Request, RequestSize) < RequestSize){
    CloseFile(); return -3;   // Error writing request
  }

  b = read(sock, Buffer, 1);    // Receive response
  if(b < 1){
    CloseFile(); return -4;   // Error receiving response
  }
  if(Buffer[0] != '\0'){
    CloseFile(); return -5;   // Server returned error to Get request
  }

  b = nread(sock, &FileSize, 4);   // Receive file size
  if(b < 4){
    CloseFile(); return -6;   // Error receiving filesize
  }
    
  count = 0;
  //printf("  Receiving...");
  while(count < FileSize){
    b = read(sock, Buffer, BUFFERSIZE);
    if(b < 1){
      printf("  Error: transmission problem.\n");
      CloseFile(); return -8;   // Error receiving file
    }
    fwrite(Buffer, 1, b, file);
    count += b;
    if(count > FileSize){
      printf("  Error: received more bytes than expected.\n");
      CloseFile(); return -9;   // Received more bytes than expected!
    }
  }
  //printf("  Done.\n");
  fclose(file);
  return 1;
}


int Put(char* localname, char* target)
{
  // Put a file on the robot.

  struct PutRequestStruct {
    char           CommandByte;
    char           FileSize[4]; // has to be array to avoid aligning
    unsigned char  NameLength;
    char           Name[256];
  } Request;

  int error, RequestSize, b;
  unsigned long count;
  FILE* file;
  char Buffer[BUFFERSIZE];
  struct stat Stat;

  error = stat(localname, &Stat);
  if((error != 0) || (!S_ISREG(Stat.st_mode))){
    return -1;  // Invalid filename
  }

  file = fopen(localname, "r");
  if(file == NULL){
    return -2;  // Error opening file for reading
  }

  Request.CommandByte       = 'p';
  *(long*)Request.FileSize  = (long)Stat.st_size;
  Request.NameLength        = strlen(target);
  strcpy(Request.Name, target);


  RequestSize = Request.NameLength + 6;
  if(write(sock, &Request, RequestSize) < RequestSize){
    fclose(file); return -3;   // Error writing request
  }

  b = read(sock, Buffer, 1);     // Receive response
  if(b < 1){
    fclose(file); return -4;   // Error receiving response
  }
  if(Buffer[0] != '\0'){
    fclose(file); return 0;    // Server replied: Error creating file
  }
   
  count = 0;
  //printf("  Sending...");
  while(count < Stat.st_size){
    b = fread(Buffer, 1, BUFFERSIZE, file);
    count += b;

    if((error = write(sock, Buffer, b)) < 1){
      return -5;             // Error transferring data
    }
  }
  fclose(file);

  // File sent. Awaiting aknowledgement...
  b = read(sock, Buffer, 1);
  if(b < 1){
    return -6;  // Error receiving acknowledgement
  }
  if(Buffer[0] != 0){
    return -7;  // Server replied: Error receiving
  }
  //printf("  Done.\n");
  return 1;
}


void List_PrintListEntries(char Type, unsigned long Size, char* Name, void* Dummy)
{
  // Handler for List function used for debugging, not linked in actual program
  if(Type != '-'){
    printf("%c %8lu  %s\n", Type, Size, Name);
  }
}



// This returns true if "str" is "cmd" or "cmd:" 
int iscommand(char* str, char* cmd)
{
  int len = strlen(cmd);
  return (strncasecmp(str, cmd, len)==0 &&
          (str[len]==0 || strcmp(str+len, ":")==0));
}


char* chomp(char* str)
{
  int i;
  for(i = strlen(str)-1; i >= 0; i--){
    if((str[i] == 0xA) || (str[i] == 0xD) ||
       (str[i] == ' ') || (str[i] == '\t')){
      str[i] = '\0';
    }else{
      return str;
    }
  }

  return str;
}

int Tokenize(char* str)
{
  Tokens[0][0] = Tokens[1][0] = Tokens[2][0] = Tokens[3][0] = Tokens[4][0] = '\0';
  return sscanf(str, "%s%s%s%s%s", Tokens[0], Tokens[1], Tokens[2],
                Tokens[3], Tokens[4]);
}


void ConnectIfNeeded(void)
{
  if(sock < 0){
    if(TargetHost[0] == '\0'){
      printf("Reload: Error: Target not specified!\n");
      exit(1);
    }
    sock = Timeout_Connect(TargetHost, 50000, 8); // 8 sec timeout
        
    if(sock < 0){
      printf("Reload: Error%s: %s\n", errcause[-sock], errstring(sock));
      exit(1);
    }
  } 
}


int Option(char option)
{
  // Check whether a letter is present in the OptionsString
  return (int)strchr(OptionsString, option);
}


// This will add an entry at the end of the specified listing.
void AddEntryToListing(char type, unsigned long size, char* name, void* ListingPtr)
{
  ListEntry    Entry, Last;
  ListingBase  Listing = (ListingBase)ListingPtr;

  // This just marks the end of listing from the Aibo and should not be processed
  if(type == '-') return;

  // Check if we are dealing with the BaseEntry.
  if(Listing->LastEntryAdded == NULL){
    Entry = (ListEntry)Listing;
  }else{
    Entry = (ListEntry)malloc(sizeof(struct ListEntryStruct));
  }

  // Assign fields. RelativeName will work out to an empty string for the BaseEntry.
  Entry->Type = type;
  Entry->Size = size;
  Entry->RelativeName = strdup(name + strlen(Listing->BasePath));

  // DEBUG Just to check that everything behaves at it should...
  if(strncasecmp(name, Listing->BasePath, strlen(Listing->BasePath)) != 0){
    printf("Error! Returned full path does not match expected base path!\n");
    printf("  Full path: %s\n", name);
    printf("  Base path: %s\n", Listing->BasePath);
    exit(1);
  }

  Entry->Next     = NULL;
  Entry->Contents = NULL;
  Entry->Parent   = NULL;

  // The BaseEntry will have parent NULL. For others, look for parent dir by checking
  // this entry's name (till just before the last '/') against candidates' names.
  // This allows us to find the parent from just a sequential DFS traversal.

  if(Entry->RelativeName[0] == '/'){
    *strrchr(Entry->RelativeName, '/') = '\0';  // Just temporarily...
    for(Last = Listing->LastEntryAdded; !Entry->Parent; Last = Last->Parent){
            
      // DEBUG Just checking assumptions...
      if(Last == NULL){
        printf("Error! Parent-finding loop could not find a parent!\n");
        printf("  Looking for parent: %s\n", Entry->RelativeName);
        exit(1);
      }

      if(strcmp(Entry->RelativeName, Last->RelativeName) == 0){
        Entry->Parent = Last;
        // Found! Now add this entry at the end of its parent's Contents list.
        if(Last->Contents == NULL){
          Last->Contents = Entry;
        }else{
          Last->LastOfDir->Next = Entry;
        }
        Last->LastOfDir = Entry;
      }
    }
    *strchr(Entry->RelativeName, '\0') = '/';   // Ok, slash back in place. =)

    // DEBUG Just checking...
    if(Entry->Parent->Type != 'D'){
      printf("Error! Assigned parent is not a directory!\n");
      printf("   Parent Name: %s\n", Entry->Parent->RelativeName);
      printf("   Entry  Name: %s\n", Entry->RelativeName);
      exit(1);
    }

    // This is to avoid traversing the whole tree again to find its depth
    int   depth;
    char* slash = strchr(Entry->RelativeName, '/');
    for(depth = 0; slash != NULL; depth++) slash = strchr(slash+1, '/');
    if(depth > Listing->MaxDepth) Listing->MaxDepth = depth; 

  }

  Listing->LastEntryAdded = Entry;
}


// This differs from AddEntryToListing (above) in that we are given a parent:
// the entry is always malloc'd, the name is relative rather than absolute,
// there are no provisions for adding a BaseEntry, and the MaxDepth field of
// the listing is not updated.
ListEntry AddEntryToParent(char type, unsigned long size, char* name, ListEntry parent)
{
  ListEntry Entry = (ListEntry)malloc(sizeof(struct ListEntryStruct));
  Entry->Type = type;
  Entry->Size = size;
  Entry->RelativeName = strdup(name);
  Entry->Next = NULL;
  Entry->Parent = parent;
  Entry->Contents = NULL;
  Entry->LastOfDir = NULL;

  if(parent->Contents == NULL){
    parent->Contents = Entry;
  }else{
    parent->LastOfDir->Next = Entry;
  }
  parent->LastOfDir = Entry;
  return Entry;

}


// Set defaults for new listing
void InitListing(ListingBase Listing, char* BasePath)
{
  Listing->Remote = false;
  Listing->BasePath = strdup(BasePath);
  Listing->MaxDepth = 0;
  Listing->LastEntryAdded = NULL;
  Listing->BaseEntry.RelativeName = strdup("");
  Listing->BaseEntry.Type = '-';
  Listing->BaseEntry.Size = 0;
  Listing->BaseEntry.Next = NULL;
  Listing->BaseEntry.Parent = NULL;
  Listing->BaseEntry.Contents = NULL;
  Listing->BaseEntry.LastOfDir = NULL;

  int PathLength = strlen(BasePath);

  if(strcmp(BasePath+PathLength-2, "/*") == 0) Listing->BasePath[PathLength-2] = '\0';
  if(BasePath[PathLength-1] == '/') Listing->BasePath[PathLength-1] = '\0';
  if(strncmp(Listing->BasePath, "ms:", 3) == 0){
    strncpy(Listing->BasePath, "/ms", 3);
    Listing->Remote = true;
  }
}


// Only from the linked list in memory, not physically from the disk.
// (Note that this does not update the MaxDepth field of the ListingBase)
void RemoveEntry(ListEntry Entry)
{
  ListEntry Item;

  if(Entry == NULL) return;          // Nothing to do.
  if(Entry->Parent == NULL) return;  // Just return if this is the BaseEntry.

  Item = Entry->Parent->Contents;

  if(Item == Entry){                // This was the first entry in the dir.
    Entry->Parent->Contents = Entry->Next;
  }else{                            // Otherwise, find the entry right before us...
    while(Item->Next != Entry) Item = Item->Next;
    Item->Next = Entry->Next;       // ...and update its 'Next' pointer.
    if(Item->Next == NULL){       // If we were last, update 'LastOfDir' pointer.
      Entry->Parent->LastOfDir = Item;
    }
  }

  free(Entry->RelativeName);
  free(Entry);
}


// Returns a complete name for the entry, usable for copy, delete, etc...
char* FullName(ListEntry Entry)
{
  ListEntry Base;

  FullNameIndex = (FullNameIndex + 1) % 2;
     
  for(Base = Entry; Base->Parent; Base = Base->Parent);
  strcpy(FullNameBuffers[FullNameIndex], ((ListingBase)Base)->BasePath);
  return strcat(FullNameBuffers[FullNameIndex], Entry->RelativeName);
}



// Returns the name starting from the last slash (included, for strcat convenience).
char* FileName(ListEntry 
Entry){
  char* Result = strrchr(Entry->RelativeName, '/');
  if(Result == NULL) return Entry->RelativeName;
  return Result;
}


// Checks if a file or dir is also present withing the given Dir.
// 'Name' is only the file/dir name, without any path. The comparison
// is case insensitive as it will happen across the two platforms.
ListEntry FindEntryInDir(char* EntryName, ListEntry Dir)
{
  ListEntry Entry;
  char*     Name;

  // Get rid of any path in the name, if there is one
  Name = strrchr(EntryName, '/');
  if(Name == NULL) Name = EntryName;

  for(Entry = Dir->Contents; Entry != NULL; Entry = Entry->Next){
    if(strcasecmp(Name, FileName(Entry)) == 0){
      return Entry; // Found. Return the entry.
    }
  }
  return NULL; // Sorry, not found.
}


int CopyLocalFileToRemoteFile(char* FullSourceName, char* FullDestName)
{
  int Result;

  ConnectIfNeeded();

  Result = Put(FullSourceName, FullDestName);

  if(Result < 1){
    printf("Error putting file: %s\n", PutErrStr[-Result]);
    printf(" -> %s\n", FullSourceName);
    return -1;
  }

  return 0;
}


int CopyRemoteFileToLocalFile(char* FullSourceName, char* FullDestName)
{
  int Result;

  ConnectIfNeeded();

  Result = Get(FullSourceName, FullDestName);
 
  if(Result < 1){
    printf("Error getting file: %s\n", GetErrStr[-Result]);
    printf(" -> %s\n", FullSourceName);
    return -1;
  }

  return 0;
}


// General version that can be used both from Aibo to PC and vice versa.
// It will figure out which and call one of the two functions above.
int CopyFileAs(char* FullSourceName, char* FullDestName)
{
  bool SourceRemote, DestRemote;

  SourceRemote = (strncasecmp(FullSourceName, "/ms/", 4) == 0);
  DestRemote   = (strncasecmp(FullDestName,   "/ms/", 4) == 0);

  if(SourceRemote == DestRemote) return -1;
  if(SourceRemote){
    return CopyRemoteFileToLocalFile(FullSourceName, FullDestName);
  }else{
    return CopyLocalFileToRemoteFile(FullSourceName, FullDestName);
  }
}


// This takes a node for the source file/dir, a node for the dir, and
// calls the appropriate physical transfer routine to copy the file
// to the desired directory.
int CopyFileToDir(ListEntry From, ListEntry ToDir)
{
  char       FullDestName[256];

  // Make the dest full name as: (Full Dest Dir) + (Filename Part only of Source File) 
  strcpy(FullDestName, FullName(ToDir));
  strcat(FullDestName, FileName(From));

  return CopyFileAs(FullName(From), FullDestName);
}


int MakeRemoteDir(char* FullDirName)
{
  char buf[256];
  int  result;

  buf[0] = 'm';
  buf[1] = strlen(FullDirName);
  strcpy(buf+2, FullDirName);
    
  ConnectIfNeeded();

  if(write(sock, &buf, 2+buf[1]) < 1){
    printf("Warning: Error sending mkdir command!\n -> %s\n", FullDirName);
    return -1;
  }
  result = read(sock, buf, 1);
  if(result < 1){
    printf("Warning: Error receiving mkdir response!\n -> %s\n", FullDirName);
    return -2;
  }
  if(buf[0] != 0){
    printf("Warning: Server replied: Cannot make specified dir! -> %s\n, FullDirName", FullDirName);
    return -3;
  }

  if(Option('v')) printf("Made remote dir: %s\n", FullDirName);
  return 0;
}


int MakeLocalDir(char* FullDirName)
{
  int     Result;
  mode_t  mode = 0777;

  Result = mkdir(FullDirName, mode);
  if((Result == 0) && Option('v')) printf("Made local dir: %s\n", FullDirName);
  return Result;
}


// Will make a directory with the 'From' name in the directory specified by the
// ToDir node. 
int MakeDir(ListEntry From, ListEntry ToDir)
{
  ListEntry Base;
  char FullDestName[256];

  strcpy(FullDestName, FullName(ToDir));
  strcat(FullDestName, FileName(From));

  for(Base = From; Base->Parent; Base = Base->Parent);
  if(((ListingBase)Base)->Remote){
    return(MakeLocalDir(FullDestName));
  }else{
    return(MakeRemoteDir(FullDestName));
  }
}


// Similar to MakeDir above, but allows to give the destination dir
// a different name than the original did
// Here 'From' is only used to check whether dir is local or remote
int MakeDirAs(ListEntry From, char* FullDestName)
{
  ListEntry Base;

  for(Base = From; Base->Parent; Base = Base->Parent);
  if(((ListingBase)Base)->Remote){
    return(MakeLocalDir(FullDestName));
  }else{
    return(MakeRemoteDir(FullDestName));
  }
}


// Should we consent to transferring 'Source' as 'Target' ? 
// This depends on command issued (Copy or Mirror), switches, type of files, size, etc... 
bool ShouldOverwrite(ListEntry Target, ListEntry Source)
{
  ListEntry Base;
  char      answer[256];

  if(Target == NULL) return true;  // If Target does not exist, yes, create it.
  if(Target->Type != 'F') return false;  // If Target is not a file, no.
  if(Option('n')) return false;    // With 'new' option, only new files are transferred
  if(Option('s') && (Target->Size == Source->Size)){
    return false;                 // With 'size' option, if sizes are equal, no.
  }

  // Check if file is local, and if so ask for permission
  for(Base = Target; Base->Parent; Base = Base->Parent);
  if(((ListingBase)Base)->Remote == false){
    if(Option('Q')) return false;
    if(!Option('A')){
      while(true){
        printf("Warning: %s already exists.  ", FullName(Target));
        printf("Are you sure you want to overwrite it? (y/n/a/q) ");
        fgets(answer, 256, stdin);
        chomp(answer);
        if(strcasecmp(answer, "y") == 0){
          return true;
        }
        if(strcasecmp(answer, "n") == 0){
          return false;
        }
        if(strcasecmp(answer, "a") == 0){
          strcat(OptionsString, "A");
          return true;
        }
        if(strcasecmp(answer, "q") == 0){
          strcat(OptionsString, "Q");
          return false;
        }
      }
    }
  }

  return true;                      // Otherwise, yes. We want to refresh every file.
}


// Transfer the given tree entry (and all of its subtree) to the specified dir
// Only call this with a ToDir that actually exists, no checking is done on that.
int TransferEntry(ListEntry From, ListEntry ToDir)
{
  ListEntry TargetEntry = FindEntryInDir(From->RelativeName, ToDir);

  if(From->Type == 'F'){

    if(TargetEntry == NULL){
      // Target does not exist. Write it.
      if(CopyFileToDir(From, ToDir) == 0){
        TargetEntry = AddEntryToParent('F', From->Size, From->RelativeName, ToDir);
        if(Option('v')){
          printf("Copied %s to %s ", FullName(From), FullName(TargetEntry));
          printf("(%lu bytes)\n", From->Size);
        }
      }else{
        printf("Warning: Error copying file\n");
        printf(" -> %s%s\n", FullName(ToDir), FileName(From));
        return -1;
      }
    } else if((TargetEntry->Type == 'F') && (ShouldOverwrite(TargetEntry, From))){
      if(CopyFileAs(FullName(From), FullName(TargetEntry)) == 0){
        RemoveEntry(TargetEntry);
        TargetEntry = AddEntryToParent('F', From->Size, From->RelativeName, ToDir);
        if(Option('v')){
          printf("Copied %s to %s ", FullName(From), FullName(TargetEntry));
          printf("(%lu bytes)\n", From->Size);
        }
      } 
    } else if(TargetEntry->Type == 'F'){
      // Target exists and is a file, but we don't want to overwrite it.
      if(Option('v')){
        printf("Skipping existing file: %s\n", FullName(TargetEntry));
      }
    }else{
      // Target exists but is a directory. That's an error.
      printf("Warning: Skipping file: name already exists but is a directory\n");
      printf(" -> %s\n", FullName(TargetEntry));
      return -2;
    }

  }else{   // if <now source was a directory>

    if(TargetEntry == NULL){  // Target does not exist
      if(MakeDir(From, ToDir) == 0){
        TargetEntry = AddEntryToParent('D', 0, From->RelativeName, ToDir);
      }else{
        printf("Warning: Error creating dir\n -> %s\n", FullName(TargetEntry));
        return -3;
      }
    } else if(TargetEntry->Type == 'F'){
      // Target exists but it's a file. That's an error.
      printf("Warning: Skipping dir: name already exists but is a file\n");
      printf(" -> %s\n", FullName(TargetEntry));
      return -4;
    }else{
      // Target exists and is a directory already. Nothing to do.
      if(Option('v')){
        printf("Processing existing directory: %s\n", FullName(TargetEntry)); 
      }
    }

    // Dir existed or was successfully created. Now recursively process contents.

    ListEntry Item;
    int Result;

    for(Item = From->Contents; Item != NULL; Item = Item->Next){
      Result = TransferEntry(Item, TargetEntry);
      if((Option('q')) && (Result != 0)) return Result;
    }
        
  }  // if <source was file or directory> 
    
  return 0;   // Success!
}


// Contact the Aibo to get listing of the desired ditectory tree and depth
char GetRemoteListing(ListingBase list, int depth)
{
  int Result;
  ConnectIfNeeded();
  Result = List(list->BasePath, depth, AddEntryToListing, list);
  if(Result == 0){
    return '-';  // Not found.
  }else if(Result < 0){
    printf("Reload: Error getting remote tree listing: %s\n", ListErrStr[-Result]);
    printf(" -> %s\n", list->BasePath);
    return '-';
  }else{
    return(list->BaseEntry.Type);
  }
}


// Same as above, but with local computer
char GetLocalListing(ListingBase list, char* dir, int depth)
{
  DIR*            thisdir;
  struct dirent*  entry;
  char            subdir[256];
  int             error;
  struct stat     Stat;

  error = lstat(dir, &Stat);
  if(error != 0) return '-';     // Not found. Should only happen on first request.

  if(S_ISREG(Stat.st_mode)){
    AddEntryToListing('F', (unsigned long)Stat.st_size, dir, list);
  }

  if(!S_ISDIR(Stat.st_mode)) return '-';

  thisdir = opendir(dir);
  AddEntryToListing('D', 0, dir, list);

  while((depth > 0) && ((entry = readdir(thisdir)) != NULL)){
    if((strcmp(entry->d_name, ".") != 0) && (strcmp(entry->d_name, "..") != 0)){ 
      sprintf(subdir, "%s/%s", dir, entry->d_name);
      GetLocalListing(list, subdir, depth-1);
    }
  }
  closedir(thisdir);
  return('D');
}


// General version of the two functions above. Will pick the appropriate one
char GetListing(ListingBase List, int depth)
{
  if(List->Remote){
    return GetRemoteListing(List, depth);
  }else{
    return GetLocalListing(List, List->BasePath, depth);
  }
}


// Free all the memory taken up by a directory listing
void FreeListing(ListingBase Base)
{
  RemoveEntry((ListEntry)Base);   // Voila'. Easy, uh? :)
}


void PrintEntry(ListEntry Entry)
{
  // For debus purposes
  if(Entry == NULL) return;
  printf("%c %8lu   %s\n", Entry->Type, Entry->Size, FullName(Entry));
  if(Entry->Type == 'D'){
    for(Entry = Entry->Contents; Entry != NULL; Entry = Entry->Next){
      PrintEntry(Entry);
    }
  }
}


// 'Type' is used to see if it's a file or a directory
int RemoveRemoteFileOrDir(char* FullDestName, char Type)
{
  char Ack, CommandBuffer[256];
  int  Result;

  CommandBuffer[0] = 'd';                  // 'd' is for delete (files only)
  if(Type == 'D') CommandBuffer[0] = 'v';  // 'v' is for remove (dirs only)
  CommandBuffer[1] = strlen(FullDestName);
  strcpy(CommandBuffer + 2, FullDestName);

  if(write(sock, CommandBuffer, 2 + CommandBuffer[1]) < 1){
    printf("Reload: Error sending command!\n -> Remove: %s\n", FullDestName);
    return -1;
  }
  Result = read(sock, &Ack, 1);
  if(Result < 1){
    printf("Reload: Error receiving response!\n -> Remove: %s\n", FullDestName);
    return -2;
  }
  if(Ack != 0){
    printf("Reload: Server replied: Cannot remove specified object!\n");
    printf(" -> Remove: %s\n", FullDestName);
    return -3;
  }

  if(Option('v')) printf("Removed remote %s\n", FullDestName);
  return 0;
}


//For local disk, we don't have to specify whether a file it'is a file of a directory 
int RemoveLocalFileOrDir(char* FullDestName)
{
  char answer[256];
  int  Result;

  if(Option('Q')) return 1;
  if(!Option('A')){
    while(true){
      printf("Warning: %s is local.  ", FullDestName);
      printf("Are you sure you want to remove it? (y/n/a/q) ");
      fgets(answer, 256, stdin);
      chomp(answer);
      if(strcasecmp(answer, "y") == 0){
        break;
      }
      if(strcasecmp(answer, "n") == 0){
        return 1;
      }
      if(strcasecmp(answer, "a") == 0){
        strcat(OptionsString, "A");
        break;
      }
      if(strcasecmp(answer, "q") == 0){
        strcat(OptionsString, "Q");
        return 1;
      }
    }
  }
  Result = remove(FullDestName);
  if((Result == 0) && Option('v')) printf("Removed local %s\n", FullDestName);
  return Result;
}


// Local or remote
int RemoveFileOrDir(ListEntry Entry, ListingBase GivenBase)
{
  ListEntry    Item;
  char         FullDestName[256];
  int          Result;
  ListingBase  Base;

  // Just find the Base once and pass it on to the recursive calls...
  Base = GivenBase;
  if(GivenBase == NULL){
    for(Item = Entry; Item->Parent; Item = Item->Parent);
    Base = (ListingBase)Item;
  }

  // Get the full path
  strcpy(FullDestName, Base->BasePath);
  strcat(FullDestName, Entry->RelativeName);

  // If it's a dir, recursively remove contents first
  if(Entry->Type == 'D'){
    for(Item = Entry->Contents; Item != NULL; Item = Item->Next){
      Result = RemoveFileOrDir(Item, Base);
      // Abort operation on error if '(q)uit' was specified
      if((Option('q')) && (Result != 0)) return Result;
    }
  }

  // Take the appropriate action to remove item
  if(Base->Remote){
    Result = RemoveRemoteFileOrDir(FullDestName, Entry->Type);
  }else{
    Result = RemoveLocalFileOrDir(FullDestName);
  }

  // Remove entry from the Listing tree
  if(Result == 0) RemoveEntry(Entry);

  return Result;
}


// This will do both Mirror and Copy, depending on whether the last parameter is true 
int MirrorDir(ListEntry FromDir, ListEntry OntoDir, bool DeleteFilesInExcess)
{
  ListEntry Item, SourceEntry;
  int Result;

  // Delete files and dirs in excess if requested
  if(DeleteFilesInExcess){
    for(Item = OntoDir->Contents; Item != NULL; Item = Item->Next){
      SourceEntry = FindEntryInDir(Item->RelativeName, FromDir);
      if((SourceEntry == NULL) || (SourceEntry->Type != Item->Type)){
        RemoveFileOrDir(Item, NULL);
      } else if(Item->Type == 'D'){
        Result = MirrorDir(SourceEntry, Item, DeleteFilesInExcess);
        if((Option('q')) && (Result != 0)) return Result;
      }
    }
  }

  // Now Transfer all files and folders that have to be transferred
  for(SourceEntry = FromDir->Contents; SourceEntry; SourceEntry = SourceEntry->Next){
    if((SourceEntry->Type != 'D') ||
        (FindEntryInDir(SourceEntry->RelativeName, OntoDir) == NULL)){
      Result = TransferEntry(SourceEntry, OntoDir);
      if((Option('q')) && (Result != 0)) return Result;
    }
  }

  return 0;
}


// Execute a mirrror or copy command
void Mirror(bool DeleteFilesInExcess)
{
  int    i;
  struct ListingBaseStruct Source, Dest;

  // Skip option tokens to get to paths
  for(i = 1; (i < MAXTOKENS - 3) && (Tokens[i][0] == '-'); i++)
    ;

  // Check for malformed command (without "to")
  if(strcasecmp(Tokens[i+1], "to") != 0){
    printf("Reload: Error: Destination must be preceded by \"to\"\n -> %s\n", line);
    return;
  }

  // We don't want wildcards in the destination
  if(strchr(Tokens[i+2], '*') != NULL){
    printf("Reload: Error: Invalid destination\n -> %s\n", line);
    return;
  }

  InitListing(&Source, Tokens[i]);  // This will also set the internal boolean
  InitListing(&Dest, Tokens[i+2]);  //  'Remote' variable according to the path

  // Check for both or no entries being remote
  if(Source.Remote && Dest.Remote){
    printf("Reload: Error: Source or destination must be local\n -> %s\n", line);
    return;
  }
  if(!Source.Remote && !Dest.Remote){
    printf("Reload: Error: Source or destination must be remote\n -> %s\n", line);
    return;
  }

  // Get the source listing, with a recursion depth of 100 (i.e: get all)
  GetListing(&Source, 100);

  // If no file or directory with that name was found, error
  if(Source.BaseEntry.Type == '-'){
    printf("Reload: Error: Source does not exist\n -> %s\n", line);
    FreeListing(&Source);
    return;
  }

  if(DeleteFilesInExcess){
    printf("Mirroring %s as %s...\n", Tokens[i], Tokens[i+2]);
  }else{
    printf("Copying %s to %s...\n", Tokens[i], Tokens[i+2]);
  }

  // Now get the dest listing, only to the required depth.
  if(DeleteFilesInExcess){
    GetListing(&Dest, 100);
  }else{
    GetListing(&Dest, Source.MaxDepth);
  }


  /*
    printf("============  SOURCE BEFORE  ============\n");
    PrintEntry((ListEntry)&Source);
    printf("============ <source before> ============\n");
    printf("\n\n");
    printf("=============  DEST BEFORE  =============\n");
    PrintEntry((ListEntry)&Dest);
    printf("============= <dest before> =============\n");
    printf("\n\n\n\n");
  */


  switch(2*Source.BaseEntry.Type - Dest.BaseEntry.Type){

    case (2*'D' - 'D'):
      // Two dirs
      MirrorDir(&Source.BaseEntry, &Dest.BaseEntry, DeleteFilesInExcess);
      break;

    case (2*'D' - 'F'):
      printf("Reload: Error: Can't copy or mirror a directory onto a file\n");
      printf(" -> %s\n", line);
      break;

    case (2*'F' - 'D'):
      // If source is a file and dest is a dir, modify paths to use TransferEntry
      free(Source.BaseEntry.RelativeName);
      Source.BaseEntry.RelativeName = strdup(strrchr(Source.BasePath, '/'));
      *strrchr(Source.BasePath, '/') = '\0';
      TransferEntry(&Source.BaseEntry, &Dest.BaseEntry);
      break;

    case (2*'F' - 'F'):
      // If both source and dest are files, transfer if appropriate
      if(ShouldOverwrite(&Dest.BaseEntry, &Source.BaseEntry)){
        if(CopyFileAs(Source.BasePath, Dest.BasePath) != 0){
          printf("Error writing file\n -> %s\n", Dest.BasePath); 
        }
        if(Option('v')){
          printf("Copied %s to %s ", Source.BasePath, Dest.BasePath);
          printf("(%lu bytes)\n", Source.BaseEntry.Size);
        }

      }
      break;

    case (2*'D' - '-'):
      if(MakeDirAs(&Source.BaseEntry, Dest.BasePath) == 0){
        MirrorDir(&Source.BaseEntry, &Dest.BaseEntry, DeleteFilesInExcess);
      }else{
        printf("Error creating dir\n -> %s\n", Dest.BasePath);
      }
      break;

    case (2*'F' - '-'):
      if(CopyFileAs(Source.BasePath, Dest.BasePath) != 0){
        printf("Error making file\n -> %s\n", Dest.BasePath); 
      }
      if(Option('v')){
        printf("Copied %s to %s ", Source.BasePath, Dest.BasePath);
        printf("(%lu bytes)\n", Source.BaseEntry.Size);
      }
      break;

  }


  /*
    printf("\n\n\n");
    printf("=============  SOURCE AFTER  ============\n");
    PrintEntry((ListEntry)&Source);
    printf("============= <source after> ============\n");
    printf("\n\n");
    printf("==============  DEST AFTER  =============\n");
    PrintEntry((ListEntry)&Dest);
    printf("============== <dest after> =============\n");
  */

  FreeListing(&Source);
  FreeListing(&Dest);
}


void Reboot(void)
{
  // Send reboot command
  char Command = 'r';
  ConnectIfNeeded();
  if(write(sock, &Command, 1) < 1){
    printf("Reload: Error sending Reboot command.\n");
    exit(1);
  }
  printf("Aibo rebooting...\n");
}


void Shutdown(void)
{
  // Send shutdown command
  char Command = 'x';
  ConnectIfNeeded();
  if(write(sock, &Command, 1) < 1){
    printf("Reload: Error sending Shutdown command.\n");
    exit(1);
  }
  printf("Aibo shutting down.\n");
}


void ParseCommandOptions(void)
{
  // Removes spaces and dashes
  int i = 1;

  while((Tokens[i][0] == '-') && (i < MAXTOKENS)){
    strcat(OptionsString, Tokens[i++]+1);
  }
}


void ResetCommandOptions(void)
{
  strcpy(OptionsString, DefaultOptions);
}


// Synchronizes date, time and timezone with Aibo
int SetClock(void)
{
  char buf[6];
  int Result;
  struct timeval tv;
  struct timezone tz;
  gettimeofday(&tv, &tz);
  tv.tv_sec -= 946684800;  // offset to year 2000 (unix uses 1970)

  ConnectIfNeeded();

  buf[0] = 's';            // 's' is the command for 'setclock'

  ((long*)(buf+1))[0] = (long)tv.tv_sec;   // Load time in seconds
  buf[5] = (char)(tz.tz_minuteswest / 60); // Load offset hours
    
  if(write(sock, &buf, 6) < 1){
    printf("Warning: Error sending SetClock command!\n");
    return -1;
  }

  Result = read(sock, buf, 1);
  if(Result < 1){
    printf("Warning: Error receiving SetClock response!\n");
    return -2;
  }

  if(buf[0] != 0){
    printf("Warning: Server replied: Could not set time!\n");
    return -3;
  }

  printf("Remote clock set.\n");
  return 0;
}


void Remove(void)
{
  // Execute a Remove command. Not documented.
  struct ListingBaseStruct Dest;

  InitListing(&Dest, Tokens[1]);
  if(GetListing(&Dest, 100) != '-'){
    printf("Removing %s...\n", Dest.BasePath);
    RemoveFileOrDir((ListEntry)&Dest, &Dest);
  }
}


void PrintHelp(void)
{
  printf("\n");
  printf("Aibo Reload %s, by Francesco Tamburrino, Carnegie Mellon University, 2004.\n", VERSION);
  printf("Reloading and updating utility for the Aibo platform.\n");
  printf("\n");
  printf("      Usage:  reload [OPTIONS] [-f configfile] [Section]\n");
  printf("\n");
  printf("\n");
  printf("Reload is a utility to update code, data and settings to or from the Aibo\n");
  printf("platform through the wireless network, reading commands and directives from\n");
  printf("a configuration file.\n");
  printf("\n");
  printf("If the '-f' option is not used, Reload will look for a 'reload.cfg' file in\n");
  printf("the current directory. See below for a description of configuration files.\n");
  printf("\n");
  printf("Note: the RELOAD.BIN object needs to be running on the Aibo.\n");
  printf("\n");
  printf("\n");
  printf("==============================\n");
  printf("   Commands and directives:   \n");
  printf("==============================\n");
  printf("\n");
  printf("   TARGET: <ip name or address>         (Target: mypuppy1.my-aibos.net)\n");
  printf("   MIRROR [OPTIONS] <Source> to <Dest>  (Mirror MS/OPEN-R/MW to ms:/open-r/mw)\n");
  printf("   COPY   [OPTIONS] <Source> to <Dest>  (Copy   ms:/logs to /dogs/logs)\n");
  printf("   SETCLOCK\n");
  printf("   REBOOT\n");
  printf("   SHUTDOWN\n");
  printf("\n");
  printf("   <Source> and <Dest> can be files or directories, either local or remote.\n");
  printf("   Paths on the remote memory stick begin with \"ms:/\"\n");
  printf("\n");
  printf("   TARGET specifies which address to use for any subsequent commands. Note\n");
  printf("       that more than one system can be updated in a single session by just\n");
  printf("       providing any relevant commands under new TARGET statements.\n");
  printf("\n");
  printf("   MIRROR will take care of updating or deleting files, folders and\n");
  printf("       subfolders so as to make <Dest> an exact replica of <Source>.\n");
  printf("\n");
  printf("   COPY will transfer files or directories. If <Source> is a directory, all\n");
  printf("       of its contents are copied as well.  Unlike MIRROR, it will not\n");
  printf("       delete excess files.\n");
  printf("\n");
  printf("   SETCLOCK will set the Aibo's internal time and timezone settings to\n");
  printf("       match those of the local system. Although file timestamps are not\n");
  printf("       readable on the Aibo, they are kept and can be read when the memory\n");
  printf("       stick is accessed through a pc. Setting the date will make the file\n");
  printf("       timestamps accurate. \n");
  printf("\n");
  printf("   REBOOT and SHUTDOWN will respectively reboot and shutdown the Aibo.\n");
  printf("\n");
  printf("\n");
  printf("==============\n");
  printf("   Options:   \n");
  printf("==============\n");
  printf("\n");
  printf("   -s  By default Reload will refresh all specified files. Use this option\n");
  printf("       to skip copying of files that already exist and have the same file\n");
  printf("       size on both systems.\n");
  printf("\n");
  printf("   -n  Copy or mirror only new files, skipping any that already exist.\n");
  printf("\n");
  printf("   -v  Verbose. Will detail every file or directory copied, created, or\n");
  printf("       removed.\n");
  printf("\n");
  printf("   -A  Always replace local files. By default, Reload asks for confirmation\n");
  printf("       before replacing or deleting files on the local system. Use this\n");
  printf("       option to prevent such warnings.  USE WITH CAUTION.\n");
  printf("\n");
  printf("   All these options (except -A) can also be provided on the command line\n");
  printf("   to affect all actions executed.\n");
  printf("\n");
  printf("\n");
  printf("=========================\n");
  printf("   Configuration file:   \n");
  printf("=========================\n");
  printf("\n");
  printf("   Commands and directives are organized in sections, so that different sets\n");
  printf("   of actions can be exectuted as needed.\n");
  printf("\n");
  printf("   Any instructions located before the first section are always processed.\n");
  printf("\n");
  printf("   If no section name is specified on the command line, Reload will default\n");
  printf("   to executing the first section it finds. \n");
  printf("\n");
  printf("   Here is a sample configuration file:\n");
  printf("\n");
  printf("      ----------------------------\n");
  printf("       # Sample reload.cfg file \n");
  printf("\n");
  printf("       Target: 192.168.1.3\n");
  printf("\n");
  printf("       [Object]\n");
  printf("       Copy -v MS/OPEN-R/MW/OBJS to ms:/open-r/mw/objs\n");
  printf("\n");
  printf("       [All]\n");
  printf("       # If you do this, Make sure MS/OPEN-R/MW/ contains ALL needed files.\n");
  printf("       Mirror -v ./MS/OPEN-R/MW/ to ms:/open-r/mw/\n");
  printf("       Reboot\n");
  printf("\n");
  printf("       [Log]\n");
  printf("       Copy -s -A ms:/logfile to ./logs/errorlog\n");
  printf("\n");
  printf("       [Off]\n");
  printf("       Shutdown\n");
  printf("\n");
  printf("       [Time]\n");
  printf("       SetClock\n");
  printf("      ----------------------------\n");
  printf("\n");
  printf("   Directives and section names are not case sensitive. \n");
  printf("\n");
  printf("Please send any bug reports to francesco@cmu.edu.\n");
  printf("\n");
}


int main(int argc, char* argv[])
{
  char    ConfigFilename[256] = "reload.cfg";
  char    TargetSection[100] = "";
  FILE*   cfgfile;
  int     tokens, i;
  bool    SectionFound = false;

  // Load command line parameters and target section if specified
  for(i = 1; i < argc; i++){
    if(argv[i][0] == '-'){       // This is an option
      if(argv[i][1] == 'f'){   // This is -f, so read in config file name
        strcpy(ConfigFilename, argv[++i]);
      }else if(strcmp(argv[i]+1, "-help") == 0){
        PrintHelp();
        exit(0);
      }else{
        strcat(OptionsString, argv[i]+1);
      }
    }else{
      strcpy(TargetSection, argv[i]);
      break;
    }
  }

  // Remove any 'A' options that might have been entered.
  while(strchr(OptionsString, 'A')) *strchr(OptionsString, 'A') = ' ';

  // Abort if no config file is found
  if((cfgfile = fopen(ConfigFilename, "r")) == NULL){
    printf("Reload: No reload.cfg found. Type 'reload --help' for more info.\n");
    exit(1);
  }   


  printf("-----------------------\n");
  printf("      Aibo Reload      \n");
  printf("-----------------------\n");

  // Now start analyzing the config file...

  while(fgets(line, 256, cfgfile)){
    chomp(line);
    // Skip comments and empty lines
    if((line[0] == '\0') || (line[0] == '#')) continue;
    tokens = Tokenize(line);

    // Check for ** Target **
    if(iscommand(Tokens[0], "Target")){
      if(strcasecmp(TargetHost, Tokens[1]) != 0){
        Close();  // We need to connect to a different host.
      } 
      strcpy(TargetHost, Tokens[1]);
      continue;
    }

    // Check for ** Reboot **
    if(iscommand(line, "Reboot")){
      Reboot();
      Close();
      exit(0);
    }

    // Check for ** Shutdown **
    if(iscommand(line, "Shutdown")){
      Shutdown();
      Close();
      exit(0);
    }

    // Check for ** Mirror **
    if(iscommand(Tokens[0], "Mirror")){
      ParseCommandOptions();
      Mirror(true);
      ResetCommandOptions();
      continue;
    }

    // Check for ** Copy **
    if(iscommand(Tokens[0], "Copy")){
      ParseCommandOptions();
      Mirror(false);
      ResetCommandOptions();
      continue;
    }

    // Check for ** Setclock **
    if(iscommand(line, "SetClock")){
      SetClock();
      continue;
    }

    // Check for ** Remove **
    if(iscommand(Tokens[0], "Remove")){
      ParseCommandOptions();
      Remove();
      ResetCommandOptions();
      continue;
    }


    // Check for ** section header **
    if((line[0] == '[') && (line[strlen(line)-1] == ']')){
      strncpy(CurrentSection, line+1, strlen(line)-2);
      CurrentSection[strlen(line)-2] = '\0';

      // If this is the section we want, good, set flag and continue
      if(strcasecmp(CurrentSection, TargetSection) == 0){
        SectionFound = true;
        continue;
      }

      // If no section was specified, execute this section (the first we
      // encountered, but change var so that it will not match the rest 
      if(TargetSection[0] == '\0'){
        strcpy(TargetSection, "<SKIP>");
        SectionFound = true;
        continue;
      // Else scan until the section is found or the file is over
      }else{
        while(fgets(line, 256, cfgfile)){
          chomp(line);
          if((line[0] == '[') && (line[strlen(line)-1] == ']')){
            strncpy(CurrentSection, line+1, strlen(line)-2);
            CurrentSection[strlen(line)-2] = '\0';
            if(strcasecmp(CurrentSection, TargetSection) == 0){
              SectionFound = true;
              break;
            }
          }
        }
        continue;
      }
    }

  }

  if((TargetSection[0] != '\0') && (SectionFound == false)){
    printf("Reload: Warning: Section \"%s\" not found.\n", TargetSection);
  }

  // Close the file.
  fclose(cfgfile);

  // Close the connection.
  Close();

  return 0;
}
