/*
 * Copyright (c) 2012 Marc Alexander Lehmann <schmorp@schmorp.de>
 * 
 * Redistribution and use in source and binary forms, with or without modifica-
 * tion, are permitted provided that the following conditions are met:
 * 
 *   1.  Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *   2.  Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
 * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
 * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
 * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License ("GPL") version 2 or any later version,
 * in which case the provisions of the GPL are applicable instead of
 * the above. If you wish to allow the use of your version of this file
 * only under the terms of the GPL and not to allow others to use your
 * version of this file under the BSD license, indicate your decision
 * by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL. If you do not delete the
 * provisions above, a recipient may use your version of this file under
 * either the BSD or the GPL.
 */

#include "urlib.h"
#include "urlib.c"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>

#include "liblzf/lzf_d.c"

/* some simple? dynamic memory management for paths might save ltos of ram */
static u_handle pack_handle;
static u_handle lock_handle;
static char tmppath[MAX_PATH];

static int exe_argc;
static u_dynbuf exe_argv;
static u_dynbuf exe_args; /* actual arguments strings copied here */

static void
tmpdir (const char *dir)
{
  static int cnt;

  for (;;)
    {
//      #ifdef _WIN32
//        sprintf (tmppath, "%s/%x_%x.tmp", dir, (unsigned int)GetCurrentProcessId (), ++cnt);
//      #else
        sprintf (tmppath, "%s/t-%x_%x.tmp", dir, (unsigned int)getpid ()             , ++cnt);
//      #endif

      if (!u_mkdir (tmppath))
        return;
    }
}

static void
systemv (const char *const argv[])
{
#ifdef _WIN32
  _spawnv (P_WAIT, argv [0], argv);
#else
  pid_t pid = fork ();

  if (pid < 0)
    u_fatal ("fork failure");

  if (!pid)
    {
      execv (argv [0], (void *)argv);
      _exit (124);
    }

  int status;
  waitpid (pid, &status, 0);
#endif
}

static void
deltree (const char *path)
{
  #ifdef _WIN32
    char buf[MAX_PATH * 2 + 64];
    const char *argv[] = { getenv ("COMSPEC"), "/c", "rd", "/s", "/q", buf, 0 };
    sprintf (buf, "\"%s\"", path);
  #else
    const char *argv[] = { "/bin/rm", "-rf", path, 0 };
  #endif
  systemv (argv);
}

static char *pack_base, *pack_end;
static struct u_pack_tail *pack_tail;
static struct u_pack_hdr *pack_cur;
static char *scratch;
static unsigned int scratch_size;

#define PACK_NAME ((char *)(pack_cur + 1))
#define PACK_DATA (PACK_NAME + u_16 (pack_cur->namelen) + 1)
#define PACK_VALID pack_cur->type

static void
pack_next (void)
{
  unsigned int d = u_32 (pack_cur->datalen);

  pack_cur = (struct u_pack_hdr *)(PACK_DATA + d);
}

static void
pack_unmap (void)
{
  if (pack_base)
    {
      u_munmap (pack_base, pack_end - pack_base);
      pack_base = 0;
    }

  if (scratch)
    {
      u_free (scratch, scratch_size);
      scratch = 0;
    }
}

static int
pack_map (void)
{
  char *addr;
  unsigned int size;

#ifdef _WIN32
  BY_HANDLE_FILE_INFORMATION fi;

  if (!GetFileInformationByHandle (pack_handle, &fi))
    return 0;

  size = fi.nFileSizeLow;
#else
  size = lseek (pack_handle, 0, SEEK_END);
#endif

  addr = u_mmap (pack_handle, size);
  if (!addr)
    return 0;

  /*pack_unmap ();*/

  pack_base = addr;
  pack_end  = pack_base + size;

  pack_tail = (void *)(pack_end - sizeof (*pack_tail));

  if (memcmp (pack_tail->magic, TAIL_MAGIC, sizeof (TAIL_MAGIC) - 1))
    return 0;

  pack_cur = (struct u_pack_hdr *)(pack_end - u_32 (pack_tail->size));

  if (pack_cur->type != T_META)
    return 0;

  scratch = u_malloc (scratch_size = u_32 (pack_tail->max_uncompressed));
  if (!scratch)
    return 0;

  return 1;
}

static void
add_arg (char *arg, unsigned int len)
{
  char *addr = u_dynbuf_append (&exe_args,  arg, len);
  u_dynbuf_append (&exe_argv, &addr, sizeof (addr));
}

static void
load (void)
{
  strcpy (exe_id , PACK_NAME);
  strcpy (exe_ver, PACK_DATA);
  u_set_exe_info ();

  if (u_chdir (exe_dir))
    u_fatal ("unable to change to application data directory");

  u_handle h = u_open ("override");
  if (u_valid (h))
    {
      u_handle oh = pack_handle;

      pack_unmap ();
      pack_handle = h;

      if (pack_map ()
          && strcmp (exe_id , PACK_NAME) == 0
          && strcmp (exe_ver, PACK_DATA) <= 0)
        u_setenv ("URLADER_OVERRIDE", "override");
      else
        {
          pack_unmap ();
          pack_handle = oh;
          oh = h;
          pack_map ();
        }

      u_close (oh);
    }

  strcpy (exe_ver, PACK_DATA);
  u_set_exe_info ();
  pack_next ();

  for (;;)
    {
      if (pack_cur->type == T_ENV)
        u_setenv (PACK_NAME, PACK_DATA);
      else if (pack_cur->type == T_ARG)
        add_arg (PACK_NAME, u_16 (pack_cur->namelen) + 1);
      else
        break;

      pack_next ();
    }
 
done_env_arg:

  strcat (strcpy (tmppath, execdir), ".lck");
  lock_handle = u_lock (tmppath, 0, 1);
  if (!lock_handle)
    u_fatal ("unable to lock application instance");

  if (access (execdir, F_OK))
    {
      // does not exist yet, so unpack and move
      tmpdir (exe_dir);

      if (u_chdir (tmppath))
        u_fatal ("unable to change to new instance directory");

      for (;;)
        {
          switch (pack_cur->type)
            {
              case T_DIR:
                u_mkdir (PACK_NAME);
                break;

              case T_FILE:
                {
                  u_handle h = u_creat (PACK_NAME, pack_cur->flags & F_EXEC);
                  unsigned int dlen, len = u_32 (pack_cur->datalen);
                  char *data = PACK_DATA;

                  if (pack_cur->flags & F_LZF)
                    if (dlen = lzf_decompress (data, len, scratch, scratch_size))
                      {
                        data = scratch;
                        len  = dlen;
                      }
                    else
                      u_fatal ("unable to uncompress file data - pack corrupted?");

                  if (!u_valid (h))
                    u_fatal ("unable to unpack file from packfile - disk full?");

                  if (u_write (h, data, len) != len)
                    u_fatal ("unable to unpack file from packfile - disk full?");

                  u_fsync (h);
                  u_close (h);
                }
                break;

              case T_NULL:
                goto done;
            }

          pack_next ();
        }

done:
      if (u_chdir (datadir))
        u_fatal ("unable to change to data directory");

      u_sync ();

      if (u_rename (tmppath, execdir))
        deltree (tmppath); // if move fails, delete new, assume other process created it independently
    }

  pack_unmap ();
  u_close (pack_handle);

  if (u_chdir (execdir))
    u_fatal ("unable to change to application instance directory");

  u_setenv ("URLADER_VERSION", URLADER_VERSION);

#if 0
  // yes, this is overkill
  u_setenv ("SHLIB_PATH"        , execdir); // hpux
  u_setenv ("LIBPATH"           , execdir); // aix
  u_setenv ("LD_LIBRARY_PATH"   , execdir); // most elf systems
  u_setenv ("LD_LIBRARY_PATH_32", execdir); // solaris
  u_setenv ("LD_LIBRARY_PATH_64", execdir); // solaris
  u_setenv ("LD_LIBRARYN32_PATH", execdir); // irix
  u_setenv ("LD_LIBRARY64_PATH" , execdir); // irix
  u_setenv ("DYLD_LIBRARY_PATH" , execdir); // os sucks from apple
#endif
}

static void
execute (void)
{
  char *null = 0;
  u_dynbuf_append (&exe_argv, &null, sizeof (null));
  systemv ((const char *const *)exe_argv.addr);
}

// this argc/argv is without argv [0]
static void
doit (int argc, char *argv[])
{
  int i;

  u_setenv ("URLADER_CURRDIR", currdir);

  u_set_datadir ();
  u_mkdir (datadir);

  if (!pack_map ())
    u_fatal ("unable to map pack file - executable corrupted?");

  load ();

  while (argc--)
    {
      add_arg (*argv, strlen (*argv) + 1);
      ++argv;
    }

  execute ();
}

#ifdef _WIN32

int APIENTRY
WinMain (HINSTANCE hI, HINSTANCE hP, LPSTR argv, int command_show)
{
  if (!GetModuleFileName (hI, tmppath, sizeof (tmppath)))
    u_fatal ("unable to find executable pack");

  u_setenv ("URLADER_EXEPATH", tmppath);

  pack_handle = u_open (tmppath);
  if (!u_valid (pack_handle))
    u_fatal ("unable to open executable pack");

  if (!GetCurrentDirectory (sizeof (currdir), currdir))
    strcpy (currdir, ".");

  doit (1, &argv);

  return 0;
}

#else

int
main (int argc, char *argv[])
{
  if (!getcwd (currdir, sizeof (currdir)))
    strcpy (currdir, ".");

  {
    const char *exe_path = 0;

    if (strchr (argv [0], '/'))
      exe_path = argv [0];
    else
      {
        const char *p, *path = getenv ("PATH");

        if (!path)
          u_fatal ("unable to find executable, try running with full path.");

        for (p = path; ; )
          {
            const char *e = p;
            int l;

            while (*e && *e != ':')
              ++e;

            l = e - p;
            memcpy (tmppath, p, l);

            if (!l)
              tmppath [l++] = '.';

            tmppath [l++] = '/';

            strcpy (tmppath + l, argv [0]);

            if (!access (tmppath, X_OK))
              break;

            p = e;
            if (!*p)
              u_fatal ("unable to find executable, try running with full path.");

            ++p;
          }

        exe_path = tmppath;
      }

    pack_handle = u_open (exe_path);
    if (!u_valid (pack_handle))
      u_fatal ("unable to open executable for reading - permissions problem?");

    u_setenv ("URLADER_EXEPATH", exe_path);
  }

#if 0
  /* intersperse hostname, for whatever reason */

  if (gethostname (tmppath, sizeof (tmppath)))
    strcpy (tmppath, "default");

  u_append (datadir, tmppath);
#endif

  doit (argc - 1, argv + 1);

  return 0;
}

#endif