#ifndef URLADER # define URLADER "urlader" #endif #define URLADER_VERSION "1.0" // a decimal number, not a version string #define MAX_ARGC 32 #define MAX_ARGS 256 #include #include #include #include #include #include #include #include "liblzf/lzf_d.c" #define TAIL_MAGIC "SCHMORPPACK0" #ifdef _WIN32 #include //#include #include #include #include static DWORD dword; #define u_handle HANDLE #define u_invalid_handle 0 #define u_valid(handle) (!!handle) #define u_setenv(name,value) SetEnvironmentVariable (name, value) #define u_mkdir(path) !CreateDirectory (path, NULL) #define u_chdir(path) !SetCurrentDirectory (path) #define u_rename(fr,to) !MoveFile (fr, to) #define u_open(path) CreateFile (path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL) #define u_creat(path,exec) CreateFile (path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL) #define u_creat(path,exec) CreateFile (path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL) #define u_close(handle) CloseHandle (handle) #define u_append(path,add) PathAppend (path, add) #define u_write(handle,data,len) (WriteFile (handle, data, len, &dword, 0) ? dword : -1) #define u_fsync(handle) FlushFileBuffers (handle) #define u_sync() #define u_lockfile(path) CreateFile (path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) #define u_cloexec(handle) #else #define _GNU_SOURCE 1 #define _BSD_SOURCE 1 // the above increases our chances of getting MAP_ANONYMOUS #include #include #include #include #include #if defined (MAP_ANON) && !defined (MAP_ANONYMOUS) #define MAP_ANONYMOUS MAP_ANON #endif #ifdef PATH_MAX #define MAX_PATH (PATH_MAX < 4096 ? 4096 : PATH_MAX) #else #define MAX_PATH 4096 #endif #define u_handle int #define u_invalid_handle -1 #define u_valid(fd) ((fd) >= 0) #define u_setenv(name,value) setenv (name, value, 1) #define u_mkdir(path) mkdir (path, 0777) #define u_chdir(path) chdir (path) #define u_rename(fr,to) rename (fr, to) #define u_open(path) open (path, O_RDONLY) #define u_creat(path,exec) open (path, O_WRONLY | O_CREAT | O_TRUNC, (exec) ? 0777 : 0666) #define u_close(handle) close (handle) #define u_append(path,add) strcat (strcat (path, "/"), add) #define u_write(handle,data,len) write (handle, data, len) // on a mostly idle system, a sync at the end is certainly faster, hope for the best #define u_fsync(handle) #define u_sync() sync () #define u_lockfile(path) open (path, O_RDWR | O_CREAT, 0666) #define u_cloexec(handle) fcntl (handle, F_SETFD, FD_CLOEXEC) #endif #define u_16(ptr) (((ptr)[0] << 8) | (ptr)[1]) #define u_32(ptr) (((ptr)[0] << 24) | ((ptr)[1] << 16) | ((ptr)[2] << 8) | (ptr)[3]) /* 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 char currdir[MAX_PATH]; static char datadir[MAX_PATH]; // %AppData%/urlader static char exe_dir[MAX_PATH]; // %AppData%/urlader/EXE_ID static char execdir[MAX_PATH]; // %AppData%/urlader/EXE_ID/EXE_VER static char exe_id[MAX_PATH]; static char exe_ver[MAX_PATH]; static int exe_argc; static const char *exe_argv[MAX_ARGC]; static char exe_args[MAX_ARGS]; /* actual arguments strings copied here */ static unsigned int exe_argo; static void fatal (const char *msg) { #ifdef _WIN32 MessageBox (0, msg, URLADER, 0); #else write (2, URLADER ": ", sizeof (URLADER ": ") - 1); write (2, msg, strlen (msg)); write (2, "\n", 1); #endif _exit (1); } static void * u_malloc (unsigned int size) { void *addr; #ifdef _WIN32 HANDLE handle = CreateFileMapping (0, 0, PAGE_READWRITE, 0, size, NULL); if (!handle) return 0; addr = MapViewOfFile (handle, FILE_MAP_WRITE, 0, 0, size); CloseHandle (handle); #elif defined (MAP_ANONYMOUS) addr = mmap (0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (addr == (void *)-1) addr = 0; #else addr = malloc (size); #endif return addr; } static void u_free (void *addr, unsigned int size) { #ifdef _WIN32 UnmapViewOfFile (addr); #elif defined (MAP_ANONYMOUS) munmap (addr, size); #else free (addr); #endif } static void * u_mmap (u_handle h, unsigned int size) { void *addr; #ifdef _WIN32 HANDLE handle = CreateFileMapping (h, 0, PAGE_READONLY, 0, size, NULL); if (!handle) return 0; addr = MapViewOfFile (handle, FILE_MAP_READ, 0, 0, size); CloseHandle (handle); #else addr = mmap (0, size, PROT_READ, MAP_SHARED, h, 0); if (addr == (void *)-1) addr = 0; #endif return addr; } static void u_munmap (void *addr, unsigned int len) { #ifdef _WIN32 UnmapViewOfFile (addr); #else munmap (addr, len); #endif } 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 u_handle u_lock (const char *path, int excl, int dowait) { u_handle h; h = u_lockfile (path); if (!u_valid (h)) return h; u_cloexec (h); for (;;) { int success; // acquire the lock #ifdef _WIN32 OVERLAPPED ov = { 0 }; success = LockFileEx (h, (excl ? LOCKFILE_EXCLUSIVE_LOCK : 0) | (dowait ? 0 : LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ov); #else struct flock lck = { 0 }; lck.l_type = excl ? F_WRLCK : F_RDLCK; lck.l_whence = SEEK_SET; lck.l_len = 1; success = !fcntl (h, dowait ? F_SETLKW : F_SETLK, &lck); #endif if (!success) break; // we have the lock, now verify that the lockfile still exists #ifdef _WIN32 // apparently, we have to open the file to get its info :( BY_HANDLE_FILE_INFORMATION s1, s2; u_handle h2 = u_lockfile (path); if (!u_valid (h)) break; success = GetFileInformationByHandle (h, &s1) && GetFileInformationByHandle (h2, &s2); u_close (h2); if (!success) break; success = s1.dwVolumeSerialNumber == s2.dwVolumeSerialNumber && s1.nFileIndexHigh == s2.nFileIndexHigh && s1.nFileIndexLow == s2.nFileIndexLow; #else struct stat s1, s2; if (fstat (h, &s1) || stat (path, &s2)) break; success = s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino; #endif if (success) return h; // lock successfully acquired // files differ, close and retry - should be very rare u_close (h); } // failure u_close (h); return u_invalid_handle; } static void systemv (const char *const argv[]) { #ifdef _WIN32 _spawnv (P_WAIT, argv [0], argv); #else pid_t pid = fork (); if (pid < 0) 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); } enum { T_NULL, // 5 T_META, // 1 : exe_id, exe_ver T_ENV, // 2 : name, value T_ARG, // 3 : arg T_DIR, // 4+: path T_FILE, // 4+: path, data T_NUM }; enum { F_EXEC = 1, F_LZF = 2, F_NULL = 0 }; struct hdr { unsigned char type; unsigned char flags; unsigned char namelen[2]; unsigned char datalen[4]; }; struct tail { unsigned char max_filesize[4]; unsigned char size[4]; char magic[12]; }; static char *pack_base, *pack_end; static struct 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 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; struct tail *tail; tail = (void *)(pack_end - sizeof (*tail)); if (memcmp (tail->magic, TAIL_MAGIC, sizeof (TAIL_MAGIC) - 1)) return 0; pack_cur = (struct hdr *)(pack_end - u_32 (tail->size)); scratch = u_malloc (scratch_size = u_32 (tail->max_filesize)); if (!scratch) return 0; return 1; } static void exe_info (void) { if (!pack_map ()) fatal ("unable to locate packfile in executable - executable corrupted?"); if (pack_cur->type != T_META) fatal ("unable to locate executable metadata - executable corrupted?"); strcpy (exe_id, PACK_NAME); } static void load (void) { u_mkdir (datadir); strcpy (exe_dir, datadir); u_append (exe_dir, exe_id); u_mkdir (exe_dir); if (u_chdir (exe_dir)) fatal ("unable to change to application data directory"); u_handle h = u_open ("override"); if (u_valid (h)) { u_handle oh = pack_handle; pack_handle = h; if (!pack_map ()) { pack_handle = oh; oh = h; } u_close (oh); } if (pack_cur->type != T_META) fatal ("unable to locate override metadata"); strcpy (exe_ver, PACK_DATA); pack_next (); for (;;) { if (pack_cur->type == T_ENV) u_setenv (PACK_NAME, PACK_DATA); else if (pack_cur->type == T_ARG) { int len = u_16 (pack_cur->namelen) + 1; exe_argv [exe_argc++] = exe_args + exe_argo; memcpy (exe_args + exe_argo, PACK_NAME, len); exe_argo += len; } else break; pack_next (); } done_env_arg: strcpy (execdir, exe_dir); u_append (execdir, "i-"); strcat (execdir, exe_ver); strcat (strcpy (tmppath, execdir), ".lck"); lock_handle = u_lock (tmppath, 0, 1); if (!lock_handle) 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)) 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 fatal ("unable to uncompress file data - pack corrupted?"); if (!u_valid (h)) fatal ("unable to unpack file from packfile - disk full?"); if (u_write (h, data, len) != len) 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)) 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)) fatal ("unable to change to application instance directory"); u_setenv ("URLADER_VERSION", URLADER_VERSION); u_setenv ("URLADER_DATADIR", datadir); u_setenv ("URLADER_EXECDIR", execdir); u_setenv ("URLADER_EXE_ID" , exe_id); u_setenv ("URLADER_EXE_DIR", exe_dir); u_setenv ("URLADER_EXE_VER", exe_ver); #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) { exe_argv [exe_argc] = 0; systemv (exe_argv); } #ifdef _WIN32 int APIENTRY WinMain (HINSTANCE hI, HINSTANCE hP, LPSTR argv, int command_show) { if (!GetModuleFileName (hI, tmppath, sizeof (tmppath))) fatal ("unable to find executable pack"); pack_handle = u_open (tmppath); if (!u_valid (pack_handle)) fatal ("unable to open executable pack"); exe_info (); if (!GetCurrentDirectory (sizeof (currdir), currdir)) strcpy (currdir, "."); if (SHGetFolderPath (0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, datadir) != S_OK) fatal ("unable to find application data directory"); u_mkdir (datadir); u_append (datadir, URLADER); load (); execute (); return 0; } #else int main (int argc, char *argv[]) { char *home = getenv ("HOME"); pack_handle = u_open (argv [0]); if (!u_valid (pack_handle)) fatal ("unable to open executable pack"); exe_info (); if (!home) { struct passwd *pw; if ((pw = getpwuid (getuid ()))) home = pw->pw_dir; else home = "/tmp"; } if (!getcwd (currdir, sizeof (currdir))) strcpy (currdir, "."); u_mkdir (home); //strcat (strcat (strcpy (datadir, home), "/."), URLADER); sprintf (datadir, "%s/.%s", home, URLADER); u_mkdir (datadir); #if 0 if (gethostname (tmppath, sizeof (tmppath))) strcpy (tmppath, "default"); u_append (datadir, tmppath); #endif load (); execute (); return 0; } #endif