/*********************************************************
 * Copyright (C) 2006 VMware, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation version 2.1 and no later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *********************************************************/

/*
 * xferlogs.c --
 *
 *      Simple program based on rpctool to dump the vm-support output to
 *      the vmx log file base64 encoded, it can dump any file supplied on the 
 *      commandline to the vmx log. It also does the decoding part of it
 *      it can read the vmware.log file decode and write the encoded files in
 *      the directory it was invoked.
 * 
 *      example of a transfer found in the vmx log file.
 *      Aug 24 18:48:09: vcpu-0| Guest: >Logfile Begins : /root/install.log: ver - 1
 *      Aug 24 18:48:09: vcpu-0| Guest: >SW5zdGFsbGluZyA0NDEgcGFja2FnZXMKCkluc3RhbGxpbmcgZ2xpYmMtY29tbW9uLTIuMi41LTM0
 *      Aug 24 18:48:09: vcpu-0| Guest: >LgpJbnN0YWxsaW5nIGh3ZGF0YS0wLjE0LTEuCkluc3RhbGxpbmcgaW5kZXhodG1sLTcuMy0zLgpJ
 *      Aug 24 18:48:09: vcpu-0| Guest: >bnN0YWxsaW5nIG1haWxjYXAtMi4xLjktMi4KSW5zdGFsbGluZyBtYW4tcGFnZXMtMS40OC0yLgpJ
 *      ....
 *      ....
 *      Aug 24 18:48:10: vcpu-0| Guest: >Mi4K
 *      Aug 24 18:48:10: vcpu-0| Guest: >Logfile Ends
 *
 */

#include <stdio.h>
#include <wchar.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include "vmware.h"
#include "vmsupport.h"
#include "rpcvmx.h"
#include "rpcout.h"
#include "base64.h"
#include "str.h"
#include "strutil.h"

#include "xferlogs_version.h"
#include "embed_version.h"
VM_EMBED_VERSION(XFERLOGS_VERSION_STRING);

/* 
 * "The resultant base64-encoded data exceeds the original in length by the
 * ratio 4:3, and typically appears to consist of seemingly random characters.
 * As newlines, represented by a CR+LF pair, are inserted in the encoded data
 * every 76 characters, the actual length of the encoded data is approximately
 * 136.8% of the original." - Base64 Wiki
 * And just so that it produces 80 char output.
 */

#define BUF_BASE64_SIZE        57
#define BUF_OUT_SIZE           256
#define LOG_GUEST_MARK         "Guest: >"
#define LOG_START_MARK         ">Logfile Begins "
#define LOG_END_MARK           ">Logfile Ends "

typedef enum {
   NOT_IN_GUEST_LOGGING, 
   IN_GUEST_LOGGING
} extractMode;

#define LOG_VERSION            1

/* 
 * The function definitions of Warning and Debug were the same, So I elected
 * to do this. Or the other alternative was to define it again.
 */
#define Warning                Debug

void Debug(const char *fmt, ...);

/*
 *--------------------------------------------------------------------------
 * 
 * xmitFile --
 *
 *       This function transfers a file using the rpc channel in base64 
 *       encoding to the vmx logs.
 * 
 * Results:
 *       None.
 *
 * Side effects:
 *       The program would exit if an error occurs in Base64_Encode.
 *       Output is added to the vmx log file.
 *
 *--------------------------------------------------------------------------
 */

static void
xmitFile(char *filename) //IN : file to be transmitted.
{
   FILE *fp;
   size_t readLen;
   char buf[BUF_BASE64_SIZE];

   /* 
    * We have a unique identifier saying that this is guest dumping the 
    * output of logs and not any other logging information from the guest.
    */
   char base64B[BUF_BASE64_SIZE * 2] = ">";
   char *base64Buf = base64B + 1;

   if (!(fp = fopen(filename, "rb"))) {
      Warning("Unable to open file %s with errno %d\n", filename, errno);
      exit(-1);
   }

   //XXX the format below is hardcoded and used by extractFile
   RpcVMX_Log("%s: %s: ver - %d", LOG_START_MARK, filename, LOG_VERSION);
   while ((readLen = fread(buf, 1, sizeof buf, fp)) > 0 ) {
      if (Base64_Encode(buf, readLen, base64Buf, sizeof base64B - 1, NULL)) {
         RpcVMX_Log("%s", base64B);
      } else {
         Warning("Error in Base64_Encode\n");
         goto exit;
      }
   }
exit :
   RpcVMX_Log(LOG_END_MARK);
   fclose(fp);
}


/*
 *--------------------------------------------------------------------------
 * 
 * extractFile --
 *
 *       This function iterates through the vmx log file and for every 
 *       line which has a "Guest: >" writes the unencoded base64 output to
 *       a file, depending on the state machine.
 * 
 * Results:
 *       None
 *
 * Side effects:
 *       The program would exit if unable to read the input file.
 *       A series of decoded output files are created.
 *--------------------------------------------------------------------------
 */

static void
extractFile(char *filename) //IN: vmx log filename e.g. vmware.log
{
   FILE *fp;
   FILE *outfp = NULL;
   char buf[BUF_OUT_SIZE];
   uint8 base64Out[BUF_OUT_SIZE];
   size_t lenOut;
   char fname[256];
   char *ptrStr, *logInpFilename, *ver;
   int version;
   int filenu = 0; // output file enumerator
   extractMode state = NOT_IN_GUEST_LOGGING;


   if (!(fp = fopen(filename, "rt"))) {
      Warning("Error opening file %s, errno %d - %s \n",
              filename, errno, strerror(errno));
      exit(-1);
   }

   while (fgets(buf, sizeof buf, fp)) {

      /* 
       * The state machine determines when to open, write and close a file.
       */
      if (strstr(buf, LOG_GUEST_MARK)) {
         if (strstr(buf, LOG_START_MARK)) { //open a new output file.
            ASSERT(outfp == NULL);
            ASSERT(state == NOT_IN_GUEST_LOGGING);
            state = IN_GUEST_LOGGING;

            /*
             * read the input filename, which was the filename written by the
             * guest.
             */
            logInpFilename = strstr(buf, LOG_START_MARK);
            logInpFilename += sizeof LOG_START_MARK;
            ptrStr = strchr(logInpFilename, ':');
            *ptrStr = '\0';

            /* 
             * Ignore the filename in the log, for obvious security reasons
             * and create a new filename consiting of time and enumerator.
             */
            Str_Sprintf(fname, sizeof fname, "%d_%"FMTTIME".log",
                        filenu++, time(NULL));

            /* 
             * Read the version information, if they dont match just warn
             * and leave the outfp null, so we do process the input file, but
             * dont write anything.
             */
            ptrStr++;
            ver = strstr(ptrStr, "ver - ");
            if (!ver) {
               Warning("No version information detected\n");
            } else {
               ver = ver + sizeof "ver - " - 1;
               version = strtol(ver, NULL, 0);
               if (version != LOG_VERSION) {
                  Warning("input version %d doesnt match the\
                          version of this binary %d", version, LOG_VERSION);
               } else {
                  printf("reading file %s to %s \n", logInpFilename, fname);
                  if (!(outfp = fopen(fname, "wb"))) {
                     Warning("Error opening file %s\n", fname);
                  }
               }
            }
         } else if (strstr(buf, LOG_END_MARK)) { // close the output file.
            ASSERT(state == IN_GUEST_LOGGING);
            state = NOT_IN_GUEST_LOGGING;
            fclose(outfp);
            outfp = NULL;
         } else { // write to the output file
            ASSERT(state == IN_GUEST_LOGGING);
            if (outfp) {
               ptrStr = strstr(buf, LOG_GUEST_MARK);
               ptrStr += sizeof LOG_GUEST_MARK - 1;
               if (Base64_Decode(ptrStr, base64Out, BUF_OUT_SIZE, &lenOut)) {
                  if (fwrite(base64Out, 1, lenOut, outfp) != lenOut) {
                     Warning("Error writing output\n");
                  }
               } else {
                  Warning("Error decoding output %s\n", ptrStr);
               }
            }
         }
      }
   }
   fclose(fp);
}

static void usage(void) {
   Warning("xferlogs <options> <filename>\n");
   Warning("options - enc/dec\n");
}

int
main(int argc, char *argv[])
{
   int status;
   if (argc < 2) {
      usage();
      return -1;
   } if (argc == 2) {
      RpcVMX_Log("%s", argv[1]);
      return 0;
   }

   if (!strncmp(argv[1], "enc", 3)) {
      xmitFile(argv[2]);
   } else if(!strncmp(argv[1], "dec", 3)) {
      extractFile(argv[2]);
   } else if(!strncmp(argv[1], "upd", 3)) {
      if (argc == 3 && StrUtil_StrToInt(&status, argv[2])) {
         RpcOut_sendOne(NULL, NULL, RPC_VMSUPPORT_STATUS " %d", status);
      } else {
         return -1;
      }
   } else {
      usage();
      return -1;
   }
   return 0;
}

void
Panic(const char *fmt, ...)
{
   va_list args;
   char buf[1024];

   va_start(args, fmt);
   Str_Vsnprintf(buf, sizeof buf, fmt, args);
   va_end(args);

   fputs(buf, stderr);
   abort();
}

void
Debug(const char *fmt, ...)
{
   va_list args;
   char *buf;

   va_start(args, fmt);
   buf = (char *)Str_Vasprintf(NULL, fmt, args);
   va_end(args);

   fprintf(stderr, "xferlogs: %s\n", buf);
   fflush(stderr);
}
