MegaGlest/mk/linux/mojosetup/platform_unix.c

1559 lines
40 KiB
C

/**
* MojoSetup; a portable, flexible installation application.
*
* Please see the file LICENSE.txt in the source's root directory.
*
* This file written by Ryan C. Gordon.
*
Copyright (c) 2006-2010 Ryan C. Gordon and others.
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Ryan C. Gordon <icculus@icculus.org>
*
*/
#if PLATFORM_UNIX
#if PLATFORM_MACOSX
#include <Carbon/Carbon.h>
#undef true
#undef false
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/utsname.h>
#include <sys/mount.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <limits.h>
#include <errno.h>
#if MOJOSETUP_HAVE_SYS_UCRED_H
# ifdef MOJOSETUP_HAVE_MNTENT_H
# undef MOJOSETUP_HAVE_MNTENT_H /* don't do both... */
# endif
# include <sys/ucred.h>
#endif
#if MOJOSETUP_HAVE_MNTENT_H
# include <mntent.h>
#endif
#if MOJOSETUP_HAVE_SYS_MNTTAB_H
# include <sys/mnttab.h>
#endif
#if PLATFORM_BEOS
#define DLOPEN_ARGS 0
void *beos_dlopen(const char *fname, int unused);
void *beos_dlsym(void *lib, const char *sym);
void beos_dlclose(void *lib);
void beos_usleep(unsigned long ticks);
#define dlopen beos_dlopen
#define dlsym beos_dlsym
#define dlclose beos_dlclose
#define usleep beos_usleep
#else
#include <dlfcn.h>
#define DLOPEN_ARGS (RTLD_NOW | RTLD_GLOBAL)
#endif
#include "platform.h"
#include "gui.h"
#include "fileio.h"
static struct timeval startup_time;
boolean MojoPlatform_istty(void)
{
static boolean already_checked = false; // this never changes in a run.
static boolean retval = false;
if (!already_checked)
{
retval = isatty(0) && isatty(1) ? true : false;
already_checked = true;
} // if
return retval;
} // MojoPlatform_istty
char *MojoPlatform_currentWorkingDir(void)
{
char *retval = NULL;
size_t len;
// loop a few times in case we don't have a large enough buffer.
for (len = 128; len <= (16*1024); len *= 2)
{
retval = (char *) xrealloc(retval, len);
if (getcwd(retval, len-1) != NULL)
{
size_t slen = strlen(retval);
if (retval[slen-1] != '/') // make sure this ends with '/' ...
{
retval[slen] = '/';
retval[slen+1] = '\0';
} // if
return retval;
} // if
} // for
free(retval);
return NULL;
} // MojoPlatform_currentWorkingDir
char *MojoPlatform_readlink(const char *linkname)
{
size_t alloclen = 16;
char *retval = NULL;
char *buf = NULL;
ssize_t len = -1;
do
{
alloclen *= 2;
buf = xrealloc(buf, alloclen);
len = readlink(linkname, buf, alloclen-1);
if ( (len != -1) && (len < (alloclen-1)) ) // !error && !overflow
{
buf[len] = '\0'; // readlink() doesn't null-terminate!
retval = xrealloc(buf, (size_t) (len+1)); // shrink it down.
} // if
} while (len >= (((ssize_t)alloclen)-1)); // loop if we need bigger buf.
return retval; // caller must free() this.
} // MojoPlatform_readlink
static void *guaranteeAllocation(void *ptr, size_t len, size_t *_alloclen)
{
void *retval = NULL;
size_t alloclen = *_alloclen;
if (alloclen > len)
return ptr;
if (!alloclen)
alloclen = 1;
while (alloclen <= len)
alloclen *= 2;
retval = xrealloc(ptr, alloclen);
if (retval != NULL)
*_alloclen = alloclen;
return retval;
} // guaranteeAllocation
// This is a mess, but I'm not sure it can be done more cleanly.
static char *realpathInternal(char *path, const char *cwd, int linkloop)
{
char *linkname = NULL;
char *retval = NULL;
size_t len = 0;
size_t alloclen = 0;
if (*path == '/') // absolute path.
{
retval = xstrdup("/");
path++;
len = 1;
} // if
else // relative path.
{
if (cwd != NULL)
retval = xstrdup(cwd);
else
{
if ((retval = MojoPlatform_currentWorkingDir()) == NULL)
return NULL;
} // else
len = strlen(retval);
} // else
while (true)
{
struct stat statbuf;
size_t newlen;
char *nextpath = strchr(path, '/');
if (nextpath != NULL)
*nextpath = '\0';
newlen = strlen(path);
retval = guaranteeAllocation(retval, len + newlen + 2, &alloclen);
strcpy(retval + len, path);
if (*path == '\0')
retval[--len] = '\0'; // chop ending "/" bit, it gets readded later.
else if (strcmp(path, ".") == 0)
{
retval[--len] = '\0'; // chop ending "/." bit
} // else if
else if (strcmp(path, "..") == 0)
{
char *ptr;
retval[--len] = '\0'; // chop ending "/.." bit
ptr = strrchr(retval, '/');
if ((ptr == NULL) || (ptr == retval))
{
strcpy(retval, "/");
len = 0;
} // if
else
{
*ptr = '\0';
len -= (size_t) ((retval+len)-ptr);
} // else
} // else if
// it may be a symlink...check it.
else if (lstat(retval, &statbuf) == -1)
goto realpathInternal_failed;
else if (S_ISLNK(statbuf.st_mode))
{
char *newresolve = NULL;
if (linkloop > 255)
goto realpathInternal_failed;
linkname = MojoPlatform_readlink(retval);
if (linkname == NULL)
goto realpathInternal_failed;
// chop off symlink name for its cwd.
retval[len] = '\0';
// resolve the link...
newresolve = realpathInternal(linkname, retval, linkloop + 1);
if (newresolve == NULL)
goto realpathInternal_failed;
len = strlen(newresolve);
retval = guaranteeAllocation(retval, len + 2, &alloclen);
strcpy(retval, newresolve);
free(newresolve);
free(linkname);
linkname = NULL;
} // else if
else
{
len += newlen;
} // else
if (nextpath == NULL)
break; // holy crap we're done!
else // append a '/' before the next path element.
{
path = nextpath + 1;
retval[len++] = '/';
retval[len] = '\0';
} // else
} // while
// Shrink string if we're using more memory than necessary...
if (alloclen > len+1)
retval = (char *) xrealloc(retval, len+1);
return retval;
realpathInternal_failed:
free(linkname);
free(retval);
return NULL;
} // realpathInternal
// Rolling my own realpath, even if the runtime has one, since apparently
// the spec is a little flakey, and it can overflow PATH_MAX. On BeOS <= 5,
// we'd have to resort to BPath to do this, too, and I'd rather avoid the C++
// dependencies and headers.
char *MojoPlatform_realpath(const char *_path)
{
char *path = xstrdup(_path);
char *retval = realpathInternal(path, NULL, 0);
free(path);
return retval;
} // MojoPlatform_realpath
// (Stolen from physicsfs: http://icculus.org/physfs/ ...)
/*
* See where program (bin) resides in the $PATH. Returns a copy of the first
* element in $PATH that contains it, or NULL if it doesn't exist or there
* were other problems.
*
* You are expected to free() the return value when you're done with it.
*/
static char *findBinaryInPath(const char *bin)
{
const char *_envr = getenv("PATH");
size_t alloc_size = 64;
char *envr = NULL;
char *exe = NULL;
char *start = NULL;
char *ptr = NULL;
if ((_envr == NULL) || (bin == NULL))
return NULL;
envr = xstrdup(_envr);
exe = (char *) xmalloc(alloc_size);
start = envr;
do
{
size_t size;
ptr = strchr(start, ':'); // find next $PATH separator.
if (ptr)
*ptr = '\0';
size = strlen(start) + strlen(bin) + 2;
if (size > alloc_size)
{
char *x = (char *) xrealloc(exe, size);
alloc_size = size;
exe = x;
} // if
// build full binary path...
strcpy(exe, start);
if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
strcat(exe, "/");
strcat(exe, bin);
if (access(exe, X_OK) == 0) // Exists as executable? We're done.
{
strcpy(exe, start); // i'm lazy. piss off.
free(envr);
return(exe);
} // if
start = ptr + 1; // start points to beginning of next element.
} while (ptr != NULL);
free(exe);
free(envr);
return NULL; // doesn't exist in path.
} // findBinaryInPath
char *MojoPlatform_appBinaryPath(void)
{
const char *argv0 = GArgv[0];
char *retval = NULL;
// !!! FIXME: try /proc/$PID/exe here?
if (strchr(argv0, '/') != NULL)
retval = MojoPlatform_realpath(argv0); // argv[0] contains a path?
else // slow path...have to search the whole $PATH for this one...
{
char *found = findBinaryInPath(argv0);
if (found)
retval = MojoPlatform_realpath(found);
free(found);
} // else
return retval;
} // MojoPlatform_appBinaryPath
char *MojoPlatform_homedir(void)
{
const char *envr = getenv("HOME");
return xstrdup(envr ? envr : "/");
} // MojoPlatform_homedir
// This implementation is a bit naive.
char *MojoPlatform_locale(void)
{
char *retval = NULL;
char *ptr = NULL;
const char *envr = getenv("LANG");
if (envr != NULL)
{
retval = xstrdup(envr);
ptr = strchr(retval, '.'); // chop off encoding if explicitly listed.
if (ptr != NULL)
*ptr = '\0';
ptr = strchr(retval, '@'); // chop off extra bits if explicitly listed.
if (ptr != NULL)
*ptr = '\0';
} // if
#if PLATFORM_MACOSX
else if (CFLocaleCreateCanonicalLocaleIdentifierFromString == NULL)
retval = NULL; // !!! FIXME: 10.2 compatibility?
else if (CFLocaleCreateCanonicalLocaleIdentifierFromString != NULL)
{
CFPropertyListRef languages = CFPreferencesCopyAppValue(
CFSTR("AppleLanguages"),
kCFPreferencesCurrentApplication);
if (languages != NULL)
{
CFStringRef primary = CFArrayGetValueAtIndex(languages, 0);
if (primary != NULL)
{
CFStringRef locale =
CFLocaleCreateCanonicalLocaleIdentifierFromString(
kCFAllocatorDefault, primary);
if (locale != NULL)
{
const CFIndex len = (CFStringGetLength(locale) + 1) * 6;
ptr = (char*) xmalloc(len);
CFStringGetCString(locale, ptr, len, kCFStringEncodingUTF8);
CFRelease(locale);
retval = xrealloc(ptr, strlen(ptr) + 1);
// !!! FIXME: this may not be 100% right, but change
// !!! FIXME: xx-YY to xx_YY (lang_country).
if (retval[2] == '-')
retval[2] = '_';
if (retval[3] == '-')
retval[3] = '_';
} // if
} // if
CFRelease(languages);
} // if
} // else if
#endif
return retval;
} // MojoPlatform_locale
char *MojoPlatform_osType(void)
{
#if PLATFORM_MACOSX
return xstrdup("macosx");
#elif PLATFORM_BEOS
return xstrdup("beos"); // !!! FIXME: zeta? haiku?
#elif defined(linux) || defined(__linux) || defined(__linux__)
return xstrdup("linux");
#elif defined(__FreeBSD__) || defined(__DragonFly__)
return xstrdup("freebsd");
#elif defined(__NetBSD__)
return xstrdup("netbsd");
#elif defined(__OpenBSD__)
return xstrdup("openbsd");
#elif defined(bsdi) || defined(__bsdi) || defined(__bsdi__)
return xstrdup("bsdi");
#elif defined(_AIX)
return xstrdup("aix");
#elif defined(hpux) || defined(__hpux) || defined(__hpux__)
return xstrdup("hpux");
#elif defined(sgi) || defined(__sgi) || defined(__sgi__) || defined(_SGI_SOURCE)
return xstrdup("irix");
#elif defined(sun)
return xstrdup("solaris");
#else
# error Please define your platform.
return NULL;
#endif
} // MojoPlatform_ostype
char *MojoPlatform_osVersion(void)
{
#if PLATFORM_MACOSX
SInt32 ver, major, minor, patch;
boolean convert = false;
char *buf = NULL;
char dummy = 0;
int len = 0;
if (Gestalt(gestaltSystemVersion, &ver) != noErr)
return NULL;
if (ver < 0x1030)
convert = true; // split (ver) into (major),(minor),(patch).
else
{
// presumably this won't fail. But if it does, we'll just use the
// original version value. This might cut the value--10.12.11 will
// come out to 10.9.9, for example--but it's better than nothing.
if (Gestalt(gestaltSystemVersionMajor, &major) != noErr)
convert = true;
if (Gestalt(gestaltSystemVersionMinor, &minor) != noErr)
convert = true;
if (Gestalt(gestaltSystemVersionBugFix, &patch) != noErr)
convert = true;
} /* else */
if (convert)
{
major = ((ver & 0xFF00) >> 8);
major = (((major / 16) * 10) + (major % 16));
minor = ((ver & 0xF0) >> 4);
patch = (ver & 0xF);
} /* if */
len = snprintf(&dummy, sizeof (dummy), "%d.%d.%d",
(int) major, (int) minor, (int) patch);
buf = (char *) xmalloc(len+1);
snprintf(buf, len+1, "%d.%d.%d", (int) major, (int) minor, (int) patch);
return buf;
#else
// This information may or may not actually MEAN anything. On BeOS, it's
// useful, but on other things, like Linux, it'll give you the kernel
// version, which doesn't necessarily help.
struct utsname un;
if (uname(&un) == 0)
return xstrdup(un.release);
#endif
return NULL;
} // MojoPlatform_osversion
char *MojoPlatform_osMachine(void)
{
struct utsname un;
if (uname(&un) == 0)
return xstrdup(un.machine);
return NULL;
} // MojoPlatform_osMachine
void MojoPlatform_sleep(uint32 ticks)
{
usleep(ticks * 1000);
} // MojoPlatform_sleep
uint32 MojoPlatform_ticks(void)
{
uint64 then_ms, now_ms;
struct timeval now;
gettimeofday(&now, NULL);
then_ms = (((uint64) startup_time.tv_sec) * 1000) +
(((uint64) startup_time.tv_usec) / 1000);
now_ms = (((uint64) now.tv_sec) * 1000) + (((uint64) now.tv_usec) / 1000);
return ((uint32) (now_ms - then_ms));
} // MojoPlatform_ticks
void MojoPlatform_die(void)
{
_exit(86);
} // MojoPlatform_die
boolean MojoPlatform_unlink(const char *fname)
{
boolean retval = false;
struct stat statbuf;
if (lstat(fname, &statbuf) != -1)
{
if (S_ISDIR(statbuf.st_mode))
retval = (rmdir(fname) == 0);
else
retval = (unlink(fname) == 0);
} // if
return retval;
} // MojoPlatform_unlink
boolean MojoPlatform_symlink(const char *src, const char *dst)
{
return (symlink(dst, src) == 0);
} // MojoPlatform_symlink
boolean MojoPlatform_mkdir(const char *path, uint16 perms)
{
// !!! FIXME: error if already exists?
return (mkdir(path, perms) == 0);
} // MojoPlatform_mkdir
boolean MojoPlatform_rename(const char *src, const char *dst)
{
return (rename(src, dst) == 0);
} // MojoPlatform_rename
boolean MojoPlatform_exists(const char *dir, const char *fname)
{
boolean retval = false;
if (fname == NULL)
retval = (access(dir, F_OK) != -1);
else
{
const size_t len = strlen(dir) + strlen(fname) + 2;
char *buf = (char *) xmalloc(len);
snprintf(buf, len, "%s/%s", dir, fname);
retval = (access(buf, F_OK) != -1);
free(buf);
} // else
return retval;
} // MojoPlatform_exists
boolean MojoPlatform_writable(const char *fname)
{
return (access(fname, W_OK) == 0);
} // MojoPlatform_writable
boolean MojoPlatform_isdir(const char *dir)
{
boolean retval = false;
struct stat statbuf;
if (lstat(dir, &statbuf) != -1)
{
if (S_ISDIR(statbuf.st_mode))
retval = true;
} // if
return retval;
} // MojoPlatform_isdir
boolean MojoPlatform_issymlink(const char *dir)
{
boolean retval = false;
struct stat statbuf;
if (lstat(dir, &statbuf) != -1)
{
if (S_ISLNK(statbuf.st_mode))
retval = true;
} // if
return retval;
} // MojoPlatform_issymlink
boolean MojoPlatform_isfile(const char *dir)
{
boolean retval = false;
struct stat statbuf;
if (lstat(dir, &statbuf) != -1)
{
if (S_ISREG(statbuf.st_mode))
retval = true;
} // if
return retval;
} // MojoPlatform_isfile
void *MojoPlatform_stdout(void)
{
int *retval = (int *) xmalloc(sizeof (int));
*retval = 1; // stdout.
return retval;
} // MojoPlatform_stdout
void *MojoPlatform_open(const char *fname, uint32 flags, uint16 mode)
{
void *retval = NULL;
int fd = -1;
int unixflags = 0;
if ((flags & MOJOFILE_READ) && (flags & MOJOFILE_WRITE))
unixflags |= O_RDWR;
else if (flags & MOJOFILE_READ)
unixflags |= O_RDONLY;
else if (flags & MOJOFILE_WRITE)
unixflags |= O_WRONLY;
else
return NULL; // have to specify SOMETHING.
if (flags & MOJOFILE_APPEND)
unixflags |= O_APPEND;
if (flags & MOJOFILE_TRUNCATE)
unixflags |= O_TRUNC;
if (flags & MOJOFILE_CREATE)
unixflags |= O_CREAT;
if (flags & MOJOFILE_EXCLUSIVE)
unixflags |= O_EXCL;
fd = open(fname, unixflags, (mode_t) mode);
if (fd != -1)
{
int *intptr = (int *) xmalloc(sizeof (int));
*intptr = fd;
retval = intptr;
} // if
return retval;
} // MojoPlatform_open
int64 MojoPlatform_read(void *fd, void *buf, uint32 bytes)
{
return (int64) read(*((int *) fd), buf, bytes);
} // MojoPlatform_read
int64 MojoPlatform_write(void *fd, const void *buf, uint32 bytes)
{
return (int64) write(*((int *) fd), buf, bytes);
} // MojoPlatform_write
int64 MojoPlatform_tell(void *fd)
{
return (int64) lseek(*((int *) fd), 0, SEEK_CUR);
} // MojoPlatform_tell
int64 MojoPlatform_seek(void *fd, int64 offset, MojoFileSeek whence)
{
int unixwhence;
switch (whence)
{
case MOJOSEEK_SET: unixwhence = SEEK_SET; break;
case MOJOSEEK_CURRENT: unixwhence = SEEK_CUR; break;
case MOJOSEEK_END: unixwhence = SEEK_END; break;
default: return -1; // !!! FIXME: maybe just abort?
} // switch
return (int64) lseek(*((int *) fd), offset, unixwhence);
} // MojoPlatform_seek
int64 MojoPlatform_flen(void *fd)
{
struct stat statbuf;
if (fstat(*((int *) fd), &statbuf) == -1)
return -1;
return((int64) statbuf.st_size);
} // MojoPlatform_flen
boolean MojoPlatform_flush(void *fd)
{
return (fsync(*((int *) fd)) == 0);
} // MojoPlatform_flush
boolean MojoPlatform_close(void *fd)
{
boolean retval = false;
int handle = *((int *) fd);
// don't close stdin, stdout, or stderr.
if ((handle == 0) || (handle == 1) || (handle == 2))
{
free(fd);
return true;
} // if
if (close(handle) == 0)
free(fd);
return retval;
} // MojoPlatform_close
void *MojoPlatform_opendir(const char *dirname)
{
return opendir(dirname);
} // MojoPlatform_opendir
char *MojoPlatform_readdir(void *_dirhandle)
{
DIR *dirhandle = (DIR *) _dirhandle;
struct dirent *dent = NULL;
while ((dent = readdir(dirhandle)) != NULL)
{
if (strcmp(dent->d_name, ".") == 0)
continue; // skip these.
else if (strcmp(dent->d_name, "..") == 0)
continue; // skip these, too.
else
break; // found a valid entry, go on.
} // while
return ((dent) ? xstrdup(dent->d_name) : NULL);
} // MojoPlatform_readdir
void MojoPlatform_closedir(void *dirhandle)
{
closedir((DIR *) dirhandle);
} // MojoPlatform_closedir
int64 MojoPlatform_filesize(const char *fname)
{
int retval = -1;
struct stat statbuf;
if ( (lstat(fname, &statbuf) != -1) && (S_ISREG(statbuf.st_mode)) )
retval = (int64) statbuf.st_size;
return retval;
} // MojoPlatform_filesize
boolean MojoPlatform_perms(const char *fname, uint16 *p)
{
boolean retval = false;
struct stat statbuf;
if (stat(fname, &statbuf) != -1)
{
*p = statbuf.st_mode;
retval = true;
} // if
return retval;
} // MojoPlatform_perms
uint16 MojoPlatform_defaultFilePerms(void)
{
return 0644;
} // MojoPlatform_defaultFilePerms
uint16 MojoPlatform_defaultDirPerms(void)
{
return 0755;
} // MojoPlatform_defaultDirPerms
uint16 MojoPlatform_makePermissions(const char *str, boolean *_valid)
{
uint16 retval = 0644;
boolean valid = true;
if (str != NULL)
{
char *endptr = NULL;
long strval = strtol(str, &endptr, 8);
// complete string was a valid number?
valid = ((*endptr == '\0') && (strval >= 0) && (strval <= 0xFFFF));
if (valid)
retval = (uint16) strval;
} // if
*_valid = valid;
return retval;
} // MojoPlatform_makePermissions
boolean MojoPlatform_chmod(const char *fname, uint16 p)
{
return (chmod(fname, p) != -1);
} // MojoPlatform_chmod
char *MojoPlatform_findMedia(const char *uniquefile)
{
#if MOJOSETUP_HAVE_SYS_UCRED_H
int i = 0;
struct statfs *mntbufp = NULL;
int mounts = getmntinfo(&mntbufp, MNT_WAIT);
for (i = 0; i < mounts; i++)
{
const char *mnt = mntbufp[i].f_mntonname;
if (MojoPlatform_exists(mnt, uniquefile))
return xstrdup(mnt);
} // for
#elif MOJOSETUP_HAVE_MNTENT_H
FILE *mounts = setmntent("/etc/mtab", "r");
if (mounts != NULL)
{
struct mntent *ent = NULL;
while ((ent = getmntent(mounts)) != NULL)
{
const char *mnt = ent->mnt_dir;
if (MojoPlatform_exists(mnt, uniquefile))
{
endmntent(mounts);
return xstrdup(mnt);
} // if
} // while
endmntent(mounts);
} // if
#elif MOJOSETUP_HAVE_SYS_MNTTAB_H
FILE *mounts = fopen(MNTTAB, "r");
if (mounts != NULL)
{
struct mnttab ent;
while (getmntent(mounts, &ent) == 0)
{
const char *mnt = ent.mnt_mountp;
if (MojoPlatform_exists(mnt, uniquefile))
{
fclose(mounts);
return xstrdup(mnt);
} // if
} // while
fclose(mounts);
} // if
#else
# warning No mountpoint detection on this platform...
#endif
return NULL;
} // MojoPlatform_findMedia
void MojoPlatform_log(const char *str)
{
syslog(LOG_USER | LOG_INFO, "%s", str);
#if PLATFORM_MACOSX
// put to stdout too, if this isn't the stdio UI.
// This will let the info show up in /Applications/Utilities/Console.app
if ((GGui != NULL) && (strcmp(GGui->name(), "stdio") != 0))
printf("%s\n", str);
#endif
} // MojoPlatform_log
static boolean testTmpDir(const char *dname, char *buf,
size_t len, const char *tmpl)
{
boolean retval = false;
if ( (dname != NULL) && (access(dname, R_OK | W_OK | X_OK) == 0) )
{
struct stat statbuf;
if ( (stat(dname, &statbuf) == 0) && (S_ISDIR(statbuf.st_mode)) )
{
const size_t rc = snprintf(buf, len, "%s/%s", dname, tmpl);
if (rc < len)
retval = true;
} // if
} // if
return retval;
} // testTmpDir
void *MojoPlatform_dlopen(const uint8 *img, size_t len)
{
// Write the image to a temporary file, dlopen() it, and delete it
// immediately. The inode will be kept around by the Unix kernel until
// we either dlclose() it or the process terminates, but we don't have
// to worry about polluting the /tmp directory or cleaning this up later.
// We'll try every reasonable temp directory location until we find one
// that works, in case (say) one lets us write a file, but there
// isn't enough space for the data.
// /dev/shm may be able to avoid writing to physical media...try it first.
const char *dirs[] = { "/dev/shm", getenv("TMPDIR"), P_tmpdir, "/tmp" };
const char *tmpl = "mojosetup-plugin-XXXXXX";
char fname[PATH_MAX];
void *retval = NULL;
int i = 0;
#if PLATFORM_MACOSX
if (dlopen == NULL) return NULL; // weak symbol on older Mac OS X
#endif
#ifndef P_tmpdir // glibc defines this, maybe others.
#define P_tmpdir NULL
#endif
for (i = 0; (i < STATICARRAYLEN(dirs)) && (retval == NULL); i++)
{
if (testTmpDir(dirs[i], fname, sizeof (fname), tmpl))
{
const int fd = mkstemp(fname);
if (fd != -1)
{
const size_t bw = write(fd, img, len);
const int rc = close(fd);
if ((bw == len) && (rc != -1))
retval = dlopen(fname, DLOPEN_ARGS);
unlink(fname);
} // if
} // if
} // for
return retval;
} // MojoPlatform_dlopen
void *MojoPlatform_dlsym(void *lib, const char *sym)
{
#if PLATFORM_MACOSX
if (dlsym == NULL) return NULL; // weak symbol on older Mac OS X
#endif
return dlsym(lib, sym);
} // MojoPlatform_dlsym
void MojoPlatform_dlclose(void *lib)
{
#if PLATFORM_MACOSX
if (dlclose == NULL) return; // weak symbol on older Mac OS X
#endif
dlclose(lib);
} // MojoPlatform_dlclose
static int runScriptString(const char *str, boolean devnull, const char **_argv)
{
int retval = 127;
pid_t pid = 0;
int pipes[2];
if (pipe(pipes) == -1)
return retval;
pid = fork();
if (pid == -1) // -1 == fork() failed.
{
close(pipes[0]);
close(pipes[1]);
return retval;
} // if
else if (pid == 0) // we're the child process.
{
int argc = 0;
const char **argv = NULL;
close(pipes[1]); // close the writing end.
dup2(pipes[0], 0); // replace stdin.
if (devnull)
{
dup2(open("/dev/null", O_WRONLY), 1); // replace stdout
dup2(open("/dev/null", O_WRONLY), 2); // replace stderr
} // if
while (_argv[argc++] != NULL) { /* no-op */ }
argv = (const char **) xmalloc(sizeof (char *) * argc+3);
argv[0] = "/bin/sh";
argv[1] = "-s";
for (argc = 0; _argv[argc] != NULL; argc++)
argv[argc+2] = _argv[argc];
argv[argc+2] = NULL;
execv(argv[0], (char **) argv);
_exit(retval); // uhoh, failed.
} // else if
else // we're the parent (pid == child process id).
{
int status = 0;
size_t len = strlen(str);
boolean failed = false;
close(pipes[0]); // close the reading end.
failed |= (write(pipes[1], str, len) != len);
failed |= (close(pipes[1]) == -1);
// !!! FIXME: we need a GGui->pump() or something here if we'll block.
failed |= (waitpid(pid, &status, 0) == -1);
if (!failed)
{
if (WIFEXITED(status))
retval = WEXITSTATUS(status);
} // if
} // else
return retval;
} // runScriptString
int MojoPlatform_runScript(const char *script, boolean devnull, const char **argv)
{
int retval = 127;
char *str = NULL;
MojoInput *in = MojoInput_newFromArchivePath(GBaseArchive, script);
if (in != NULL)
{
int64 len = in->length(in);
if (len > 0)
{
str = (char *) xmalloc(len + 1);
if (in->read(in, str, len) == len)
str[len] = '\0';
else
{
free(str);
str = NULL;
} // else
} // if
in->close(in);
} // if
if (str != NULL)
retval = runScriptString(str, devnull, argv);
free(str);
return retval;
} // runScript
#if !PLATFORM_MACOSX && !PLATFORM_BEOS
static char *shellEscape(const char *str)
{
size_t len = 0;
char *retval = NULL;
const char *ptr = NULL;
char *dst = NULL;
for (ptr = str; *ptr; ptr++)
len += (*ptr == '\'') ? 4 : 1;
retval = (char *) xmalloc(len + 3); // +2 single quotes and a null char.
dst = retval;
*(dst++) = '\'';
for (ptr = str; *ptr; ptr++)
{
const char ch = *ptr;
if (ch != '\'')
*(dst++) = ch;
else
{
*(dst++) = '\'';
*(dst++) = '\\';
*(dst++) = '\'';
*(dst++) = '\'';
} // else
} // for
*(dst++) = '\'';
*(dst++) = '\0';
return retval;
} // shellEscape
static boolean unix_launchXdgUtil(const char *util, const char **argv)
{
char *path = findBinaryInPath(util);
int rc = 0;
// !!! FIXME: do I really need to be using system() and
// !!! FIXME: suffering with shell escaping?
if (path != NULL) // it's installed on the system; use that.
{
char *cmd = shellEscape(util);
char *tmp = NULL;
int i;
// just in case there's a space in the $PATH entry...
tmp = shellEscape(path);
free(path);
path = tmp;
for (i = 0; argv[i]; i++)
{
char *escaped = shellEscape(argv[i]);
tmp = format("%0 %1", cmd, escaped);
free(escaped);
free(cmd);
cmd = tmp;
} // for
tmp = format("%0/%1 >/dev/null 2>&1", path, cmd);
free(cmd);
cmd = tmp;
rc = system(cmd);
logDebug("system( %0 ) returned %1", cmd, numstr(rc));
free(cmd);
free(path);
} // if
else // try our fallback copy of xdg-utils in GBaseArchive?
{
char *script = format("meta/xdg-utils/%0", util);
rc = MojoPlatform_runScript(script, true, argv);
logDebug("internal script '%0' returned %1", script, numstr(rc));
free(script);
} // if
return (rc == 0);
} // unix_launchXdgUtil
static boolean unix_launchBrowser(const char *url)
{
const char *argv[] = { url, NULL };
return unix_launchXdgUtil("xdg-open", argv);
} // unix_launchBrowser
boolean xdgDesktopMenuItem(const char *action, const char *data)
{
// xdg-utils, being shell scripts, don't do well with paths containing
// spaces. We attempt to mitigate this by chdir()'ing to the directory
// with the file to install.
const char *ptr = strrchr(data, '/');
boolean retval = false;
if (ptr == NULL)
{
const char *argv[] = { action, data, NULL };
retval = unix_launchXdgUtil("xdg-desktop-menu", argv);
}
else
{
char *working_dir = MojoPlatform_currentWorkingDir();
if (working_dir != NULL)
{
char *cpy = xstrdup(data);
char *fname = cpy + ((size_t)(ptr-data));
const char *argv[] = { action, fname+1, NULL };
*(fname++) = '\0';
if (chdir(cpy) == 0)
{
retval = unix_launchXdgUtil("xdg-desktop-menu", argv);
if (chdir(working_dir) == -1) // deep trouble!
fatal("Failed to chdir to '%0'", working_dir);
} // if
free(cpy);
free(working_dir);
} // if
} // else
return retval;
} // xdgDesktopMenuItem
#endif
boolean MojoPlatform_launchBrowser(const char *url)
{
#if PLATFORM_MACOSX
CFURLRef cfurl = CFURLCreateWithBytes(NULL, (const UInt8 *) url,
strlen(url), kCFStringEncodingUTF8, NULL);
const OSStatus err = LSOpenCFURLRef(cfurl, NULL);
CFRelease(cfurl);
return (err == noErr);
#elif PLATFORM_BEOS
extern int beos_launchBrowser(const char *url);
return beos_launchBrowser(url) ? true : false;
#else
return unix_launchBrowser(url);
#endif
} // MojoPlatform_launchBrowser
boolean MojoPlatform_installDesktopMenuItem(const char *data)
{
#if PLATFORM_MACOSX || PLATFORM_BEOS
// !!! FIXME: write me.
STUBBED("desktop menu support");
return false;
#else
return xdgDesktopMenuItem("install", data);
#endif
} // MojoPlatform_installDesktopMenuItem
boolean MojoPlatform_uninstallDesktopMenuItem(const char *data)
{
#if PLATFORM_MACOSX || PLATFORM_BEOS
// !!! FIXME: write me.
STUBBED("desktop menu support");
return false;
#else
return xdgDesktopMenuItem("uninstall", data);
#endif
} // MojoPlatform_uninstallDesktopMenuItem
int MojoPlatform_exec(const char *cmd)
{
execl(cmd, cmd, NULL);
return errno;
} // MojoPlatform_exec
#if SUPPORT_MULTIARCH
void MojoPlatform_switchBin(const uint8 *img, size_t len)
{
const char *dirs[] = { "/dev/shm", getenv("TMPDIR"), P_tmpdir, "/tmp" };
const char *tmpl = "mojosetup-switch-bin-XXXXXX";
char fname[PATH_MAX];
int i = 0;
for (i = 0; i < STATICARRAYLEN(dirs); i++)
{
if (testTmpDir(dirs[i], fname, len, tmpl))
{
const int fd = mkstemp(fname);
if (fd != -1)
{
const size_t bw = write(fd, img, len);
const int rc = close(fd);
if ((bw == len) && (rc != -1))
{
const char *tmpstr = GArgv[0];
chmod(fname, 0700);
GArgv[0] = fname;
execv(fname, (char * const *) GArgv);
// only hits this line if process wasn't replaced.
GArgv[0] = tmpstr;
} // if
unlink(fname);
} // if
} // if
} // for
// couldn't replace current process.
} // MojoPlatform_switchBin
#endif
boolean MojoPlatform_spawnTerminal(void)
{
#if PLATFORM_BEOS
#error write me.
// "/boot/apps/Terminal"
#elif PLATFORM_MACOSX
// this is nasty...it'd be nice if Terminal.app just took command lines.
boolean failed = false;
FILE *io = NULL;
char *cmd = NULL;
char *ptr = NULL;
char *binpath = MojoPlatform_appBinaryPath();
size_t len = (strlen(binpath) * 5) + 3;
int i = 0;
for (i = 1; i < GArgc; i++)
len += (strlen(GArgv[i]) * 5) + 3;
ptr = cmd = (char *) xmalloc(len+1);
for (i = 0; i < GArgc; i++)
{
const char *str = (i == 0) ? binpath : GArgv[i];
if (i != 0)
*(ptr++) = ' ';
*(ptr++) = '\'';
while (*str)
{
const char ch = *(str++);
if (ch == '\'')
{
// have to escape for both AppleScript and /bin/sh. :/
*(ptr++) = '\'';
*(ptr++) = '\\';
*(ptr++) = '\\';
*(ptr++) = '\'';
*(ptr++) = '\'';
} // if
else
{
*(ptr++) = ch;
} // else
} // while
*(ptr++) = '\'';
} // for
free(binpath);
*ptr = '\0';
ptr = format(
"ignoring application responses\n"
"tell application \"Terminal\" to do script \"clear ; echo %0 -notermspawn=1 ; exit\"\n"
"tell application \"Terminal\" to tell its front window to set its custom title to \"MojoSetup\"\n"
"tell application \"Terminal\" to tell its front window to set its title displays device name to false\n"
"tell application \"Terminal\" to tell its front window to set its title displays shell path to false\n"
"tell application \"Terminal\" to tell its front window to set its title displays window size to false\n"
"tell application \"Terminal\" to tell its front window to set its title displays file name to false\n"
"tell application \"Terminal\" to tell its front window to set its title displays custom title to true\n"
"tell application \"Terminal\" to activate\n"
"end ignoring\n", cmd);
free(cmd);
io = popen("osascript -", "w");
if (io == NULL)
failed = true;
else
{
failed |= (fwrite(ptr, strlen(ptr), 1, io) != 1);
failed |= (pclose(io) != 0);
} // else
free(ptr);
if (!failed)
exit(0);
// we'll return false at the end to note we failed.
#else
// urgh
static const char *terms[] = {
"gnome-terminal", "konsole", "kvt", "xterm", "rxvt",
"dtterm", "eterm", "Eterm", "aterm"
};
char *binpath = MojoPlatform_appBinaryPath();
const char *tryfirst = NULL;
const int max_added_args = 5;
const unsigned int argc = GArgc + max_added_args;
const char **argv = NULL;
int i = 0;
int startarg = 0;
if (getenv("DISPLAY") == NULL)
return false; // don't bother if we don't have X.
else if (getenv("GNOME_DESKTOP_SESSION_ID") != NULL) // this is gnome?
tryfirst = "gnome-terminal";
else if (getenv("KDE_FULL_SESSION") != NULL) // this KDE >= 3.2?
tryfirst = "konsole";
argv = xmalloc((argc + 1) * sizeof(char *));
for (i = -1; i < ((int) STATICARRAYLEN(terms)); i++)
{
int is_gnome_term = false;
int argi = 0;
const char *trythis = (i == -1) ? tryfirst : terms[i];
if (trythis == NULL)
continue;
// !!! FIXME: hack. I'm sure other terminal emulators have needs, too.
is_gnome_term = (strcmp(trythis, "gnome-terminal") == 0);
argv[argi++] = trythis;
argv[argi++] = is_gnome_term ? "--title" : "-title";
argv[argi++] = "MojoSetup";
argv[argi++] = is_gnome_term ? "-x" : "-e";
argv[argi++] = binpath;
argv[argi++] = "-notermspawn=1";
assert(argi-1 <= max_added_args);
for (startarg = argi-1; argi <= argc; argi++) // include ending NULL.
{
argv[argi] = GArgv[argi - startarg];
if (argv[argi] == NULL)
break;
} // for
execvp(trythis, (char * const *) argv);
} // for
// Still here? We failed. Mankind is wiped out in the Robot Wars.
free(argv);
free(binpath);
#endif
return false;
} // MojoPlatform_spawnTerminal
uint8 *MojoPlatform_decodeImage(const uint8 *data, uint32 size,
uint32 *w, uint32 *h)
{
// !!! FIXME: try Quartz APIs on Mac OS X?
return NULL; // no platform-specific APIs. Just use the built-in ones.
} // MojoPlatform_decodeImage
uint64 MojoPlatform_getuid(void)
{
return (uint64) getuid();
} // MojoPlatform_getuid
uint64 MojoPlatform_geteuid(void)
{
return (uint64) geteuid();
} // MojoPlatform_geteuid
uint64 MojoPlatform_getgid(void)
{
return (uint64) getgid();
} // MojoPlatform_getgid
static void signal_catcher(int sig)
{
static boolean first_shot = true;
if (first_shot)
{
first_shot = false;
logError("Caught signal #%0", numstr(sig));
} // if
} // signal_catcher
static void crash_catcher(int sig)
{
signal_catcher(sig);
MojoSetup_crashed();
} // crash_catcher
static void termination_catcher(int sig)
{
signal_catcher(sig);
MojoSetup_terminated();
} // termination_catcher
static void install_signals(void)
{
static int crash_sigs[] = { SIGSEGV,SIGILL,SIGBUS,SIGFPE,SIGTRAP,SIGABRT };
static int term_sigs[] = { SIGQUIT, SIGINT, SIGTERM, SIGHUP };
static int ignore_sigs[] = { SIGPIPE };
int i;
for (i = 0; i < STATICARRAYLEN(crash_sigs); i++)
signal(crash_sigs[i], crash_catcher);
for (i = 0; i < STATICARRAYLEN(term_sigs); i++)
signal(term_sigs[i], termination_catcher);
for (i = 0; i < STATICARRAYLEN(ignore_sigs); i++)
signal(ignore_sigs[i], SIG_IGN);
} // install_signals
int main(int argc, char **argv)
{
gettimeofday(&startup_time, NULL);
openlog("mojosetup", LOG_PID, LOG_USER);
atexit(closelog);
install_signals();
return MojoSetup_main(argc, argv);
} // main
#endif // PLATFORM_UNIX
// end of unix.c ...