root/src/cmd/dist/unix.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. bprintf
  2. bpathf
  3. bwritef
  4. breadfrom
  5. xgetenv
  6. run
  7. runv
  8. bgrunv
  9. genrun
  10. bgwait1
  11. bgwait
  12. xgetwd
  13. xrealwd
  14. isdir
  15. isfile
  16. mtime
  17. isabs
  18. readfile
  19. writefile
  20. xmkdir
  21. xmkdirall
  22. xremove
  23. xremoveall
  24. xreaddir
  25. xworkdir
  26. fatal
  27. xmalloc
  28. xstrdup
  29. xrealloc
  30. xfree
  31. hassuffix
  32. hasprefix
  33. contains
  34. streq
  35. lastelem
  36. xmemmove
  37. xmemcmp
  38. xstrlen
  39. xexit
  40. xatexit
  41. xprintf
  42. errprintf
  43. xsetenv
  44. main
  45. xqsort
  46. xstrcmp
  47. xstrstr
  48. xstrrchr
  49. xsamefile
  50. xtryexecfunc
  51. sigillhand
  52. __cpuid
  53. cansse2

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// These #ifdefs are being used as a substitute for
// build configuration, so that on any system, this
// tool can be built with the local equivalent of
//      cc *.c
//
#ifndef WIN32
#ifndef PLAN9

#include "a.h"
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/utsname.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <setjmp.h>
#include <signal.h>

// bprintf replaces the buffer with the result of the printf formatting
// and returns a pointer to the NUL-terminated buffer contents.
char*
bprintf(Buf *b, char *fmt, ...)
{
        va_list arg;
        char buf[4096];
        
        breset(b);
        va_start(arg, fmt);
        vsnprintf(buf, sizeof buf, fmt, arg);
        va_end(arg);
        bwritestr(b, buf);
        return bstr(b);
}

// bpathf is the same as bprintf (on windows it turns / into \ after the printf).
// It returns a pointer to the NUL-terminated buffer contents.
char*
bpathf(Buf *b, char *fmt, ...)
{
        va_list arg;
        char buf[4096];
        
        breset(b);
        va_start(arg, fmt);
        vsnprintf(buf, sizeof buf, fmt, arg);
        va_end(arg);
        bwritestr(b, buf);
        return bstr(b);
}

// bwritef is like bprintf but does not reset the buffer
// and does not return the NUL-terminated string.
void
bwritef(Buf *b, char *fmt, ...)
{
        va_list arg;
        char buf[4096];
        
        va_start(arg, fmt);
        vsnprintf(buf, sizeof buf, fmt, arg);
        va_end(arg);
        bwritestr(b, buf);
}

// breadfrom appends to b all the data that can be read from fd.
static void
breadfrom(Buf *b, int fd)
{
        int n;

        for(;;) {
                bgrow(b, 4096);
                n = read(fd, b->p+b->len, 4096);
                if(n < 0)
                        fatal("read: %s", strerror(errno));
                if(n == 0)
                        break;
                b->len += n;
        }
}

// xgetenv replaces b with the value of the named environment variable.
void
xgetenv(Buf *b, char *name)
{
        char *p;
        
        breset(b);
        p = getenv(name);
        if(p != NULL)
                bwritestr(b, p);
}

static void genrun(Buf *b, char *dir, int mode, Vec *argv, int bg);

// run runs the command named by cmd.
// If b is not nil, run replaces b with the output of the command.
// If dir is not nil, run runs the command in that directory.
// If mode is CheckExit, run calls fatal if the command is not successful.
void
run(Buf *b, char *dir, int mode, char *cmd, ...)
{
        va_list arg;
        Vec argv;
        char *p;
        
        vinit(&argv);
        vadd(&argv, cmd);
        va_start(arg, cmd);
        while((p = va_arg(arg, char*)) != nil)
                vadd(&argv, p);
        va_end(arg);
        
        runv(b, dir, mode, &argv);
        
        vfree(&argv);
}

// runv is like run but takes a vector.
void
runv(Buf *b, char *dir, int mode, Vec *argv)
{
        genrun(b, dir, mode, argv, 1);
}

// bgrunv is like run but runs the command in the background.
// bgwait waits for pending bgrunv to finish.
void
bgrunv(char *dir, int mode, Vec *argv)
{
        genrun(nil, dir, mode, argv, 0);
}

#define MAXBG 4 /* maximum number of jobs to run at once */

static struct {
        int pid;
        int mode;
        char *cmd;
        Buf *b;
} bg[MAXBG];
static int nbg;
static int maxnbg = nelem(bg);

static void bgwait1(void);

// genrun is the generic run implementation.
static void
genrun(Buf *b, char *dir, int mode, Vec *argv, int wait)
{
        int i, p[2], pid;
        Buf cmd;
        char *q;

        while(nbg >= maxnbg)
                bgwait1();

        // Generate a copy of the command to show in a log.
        // Substitute $WORK for the work directory.
        binit(&cmd);
        for(i=0; i<argv->len; i++) {
                if(i > 0)
                        bwritestr(&cmd, " ");
                q = argv->p[i];
                if(workdir != nil && hasprefix(q, workdir)) {
                        bwritestr(&cmd, "$WORK");
                        q += strlen(workdir);
                }
                bwritestr(&cmd, q);
        }
        if(vflag > 1)
                errprintf("%s\n", bstr(&cmd));

        if(b != nil) {
                breset(b);
                if(pipe(p) < 0)
                        fatal("pipe: %s", strerror(errno));
        }

        switch(pid = fork()) {
        case -1:
                fatal("fork: %s", strerror(errno));
        case 0:
                if(b != nil) {
                        close(0);
                        close(p[0]);
                        dup2(p[1], 1);
                        dup2(p[1], 2);
                        if(p[1] > 2)
                                close(p[1]);
                }
                if(dir != nil) {
                        if(chdir(dir) < 0) {
                                fprintf(stderr, "chdir %s: %s\n", dir, strerror(errno));
                                _exit(1);
                        }
                }
                vadd(argv, nil);
                execvp(argv->p[0], argv->p);
                fprintf(stderr, "%s\n", bstr(&cmd));
                fprintf(stderr, "exec %s: %s\n", argv->p[0], strerror(errno));
                _exit(1);
        }
        if(b != nil) {
                close(p[1]);
                breadfrom(b, p[0]);
                close(p[0]);
        }

        if(nbg < 0)
                fatal("bad bookkeeping");
        bg[nbg].pid = pid;
        bg[nbg].mode = mode;
        bg[nbg].cmd = btake(&cmd);
        bg[nbg].b = b;
        nbg++;
        
        if(wait)
                bgwait();

        bfree(&cmd);
}

// bgwait1 waits for a single background job.
static void
bgwait1(void)
{
        int i, pid, status, mode;
        char *cmd;
        Buf *b;

        errno = 0;
        while((pid = wait(&status)) < 0) {
                if(errno != EINTR)
                        fatal("waitpid: %s", strerror(errno));
        }
        for(i=0; i<nbg; i++)
                if(bg[i].pid == pid)
                        goto ok;
        fatal("waitpid: unexpected pid");

ok:
        cmd = bg[i].cmd;
        mode = bg[i].mode;
        bg[i].pid = 0;
        b = bg[i].b;
        bg[i].b = nil;
        bg[i] = bg[--nbg];
        
        if(mode == CheckExit && (!WIFEXITED(status) || WEXITSTATUS(status) != 0)) {
                if(b != nil)
                        xprintf("%s\n", bstr(b));
                fatal("FAILED: %s", cmd);
        }
        xfree(cmd);
}

// bgwait waits for all the background jobs.
void
bgwait(void)
{
        while(nbg > 0)
                bgwait1();
}

// xgetwd replaces b with the current directory.
void
xgetwd(Buf *b)
{
        char buf[MAXPATHLEN];
        
        breset(b);
        if(getcwd(buf, MAXPATHLEN) == nil)
                fatal("getcwd: %s", strerror(errno));
        bwritestr(b, buf);      
}

// xrealwd replaces b with the 'real' name for the given path.
// real is defined as what getcwd returns in that directory.
void
xrealwd(Buf *b, char *path)
{
        int fd;
        
        fd = open(".", 0);
        if(fd < 0)
                fatal("open .: %s", strerror(errno));
        if(chdir(path) < 0)
                fatal("chdir %s: %s", path, strerror(errno));
        xgetwd(b);
        if(fchdir(fd) < 0)
                fatal("fchdir: %s", strerror(errno));
        close(fd);
}

// isdir reports whether p names an existing directory.
bool
isdir(char *p)
{
        struct stat st;
        
        return stat(p, &st) >= 0 && S_ISDIR(st.st_mode);
}

// isfile reports whether p names an existing file.
bool
isfile(char *p)
{
        struct stat st;
        
        return stat(p, &st) >= 0 && S_ISREG(st.st_mode);
}

// mtime returns the modification time of the file p.
Time
mtime(char *p)
{
        struct stat st;
        
        if(stat(p, &st) < 0)
                return 0;
        return (Time)st.st_mtime*1000000000LL;
}

// isabs reports whether p is an absolute path.
bool
isabs(char *p)
{
        return hasprefix(p, "/");
}

// readfile replaces b with the content of the named file.
void
readfile(Buf *b, char *file)
{
        int fd;
        
        breset(b);
        fd = open(file, 0);
        if(fd < 0)
                fatal("open %s: %s", file, strerror(errno));
        breadfrom(b, fd);
        close(fd);
}

// writefile writes b to the named file, creating it if needed.  if
// exec is non-zero, marks the file as executable.
void
writefile(Buf *b, char *file, int exec)
{
        int fd;
        
        fd = creat(file, 0666);
        if(fd < 0)
                fatal("create %s: %s", file, strerror(errno));
        if(write(fd, b->p, b->len) != b->len)
                fatal("short write: %s", strerror(errno));
        if(exec)
                fchmod(fd, 0755);
        close(fd);
}

// xmkdir creates the directory p.
void
xmkdir(char *p)
{
        if(mkdir(p, 0777) < 0)
                fatal("mkdir %s: %s", p, strerror(errno));
}

// xmkdirall creates the directory p and its parents, as needed.
void
xmkdirall(char *p)
{
        char *q;

        if(isdir(p))
                return;
        q = strrchr(p, '/');
        if(q != nil) {
                *q = '\0';
                xmkdirall(p);
                *q = '/';
        }
        xmkdir(p);
}

// xremove removes the file p.
void
xremove(char *p)
{
        if(vflag > 2)
                errprintf("rm %s\n", p);
        unlink(p);
}

// xremoveall removes the file or directory tree rooted at p.
void
xremoveall(char *p)
{
        int i;
        Buf b;
        Vec dir;

        binit(&b);
        vinit(&dir);

        if(isdir(p)) {
                xreaddir(&dir, p);
                for(i=0; i<dir.len; i++) {
                        bprintf(&b, "%s/%s", p, dir.p[i]);
                        xremoveall(bstr(&b));
                }
                if(vflag > 2)
                        errprintf("rm %s\n", p);
                rmdir(p);
        } else {
                if(vflag > 2)
                        errprintf("rm %s\n", p);
                unlink(p);
        }
        
        bfree(&b);
        vfree(&dir);    
}

// xreaddir replaces dst with a list of the names of the files in dir.
// The names are relative to dir; they are not full paths.
void
xreaddir(Vec *dst, char *dir)
{
        DIR *d;
        struct dirent *dp;
        
        vreset(dst);
        d = opendir(dir);
        if(d == nil)
                fatal("opendir %s: %s", dir, strerror(errno));
        while((dp = readdir(d)) != nil) {
                if(strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
                        continue;
                vadd(dst, dp->d_name);
        }
        closedir(d);
}

// xworkdir creates a new temporary directory to hold object files
// and returns the name of that directory.
char*
xworkdir(void)
{
        Buf b;
        char *p;
        
        binit(&b);

        xgetenv(&b, "TMPDIR");
        if(b.len == 0)
                bwritestr(&b, "/var/tmp");
        if(b.p[b.len-1] != '/')
                bwrite(&b, "/", 1);
        bwritestr(&b, "go-cbuild-XXXXXX");
        p = bstr(&b);
        if(mkdtemp(p) == nil)
                fatal("mkdtemp(%s): %s", p, strerror(errno));
        p = btake(&b);

        bfree(&b);

        return p;
}

// fatal prints an error message to standard error and exits.
void
fatal(char *msg, ...)
{
        va_list arg;
        
        fflush(stdout);
        fprintf(stderr, "go tool dist: ");
        va_start(arg, msg);
        vfprintf(stderr, msg, arg);
        va_end(arg);
        fprintf(stderr, "\n");
        
        bgwait();
        exit(1);
}

// xmalloc returns a newly allocated zeroed block of n bytes of memory.
// It calls fatal if it runs out of memory.
void*
xmalloc(int n)
{
        void *p;
        
        p = malloc(n);
        if(p == nil)
                fatal("out of memory");
        memset(p, 0, n);
        return p;
}

// xstrdup returns a newly allocated copy of p.
// It calls fatal if it runs out of memory.
char*
xstrdup(char *p)
{
        p = strdup(p);
        if(p == nil)
                fatal("out of memory");
        return p;
}

// xrealloc grows the allocation p to n bytes and
// returns the new (possibly moved) pointer.
// It calls fatal if it runs out of memory.
void*
xrealloc(void *p, int n)
{
        p = realloc(p, n);
        if(p == nil)
                fatal("out of memory");
        return p;
}

// xfree frees the result returned by xmalloc, xstrdup, or xrealloc.
void
xfree(void *p)
{
        free(p);
}

// hassuffix reports whether p ends with suffix.
bool
hassuffix(char *p, char *suffix)
{
        int np, ns;
        
        np = strlen(p);
        ns = strlen(suffix);
        return np >= ns && strcmp(p+np-ns, suffix) == 0;
}

// hasprefix reports whether p begins with prefix.
bool
hasprefix(char *p, char *prefix)
{
        return strncmp(p, prefix, strlen(prefix)) == 0;
}

// contains reports whether sep appears in p.
bool
contains(char *p, char *sep)
{
        return strstr(p, sep) != nil;
}

// streq reports whether p and q are the same string.
bool
streq(char *p, char *q)
{
        return strcmp(p, q) == 0;
}

// lastelem returns the final path element in p.
char*
lastelem(char *p)
{
        char *out;

        out = p;
        for(; *p; p++)
                if(*p == '/')
                        out = p+1;
        return out;
}

// xmemmove copies n bytes from src to dst.
void
xmemmove(void *dst, void *src, int n)
{
        memmove(dst, src, n);
}

// xmemcmp compares the n-byte regions starting at a and at b.
int
xmemcmp(void *a, void *b, int n)
{
        return memcmp(a, b, n);
}

// xstrlen returns the length of the NUL-terminated string at p.
int
xstrlen(char *p)
{
        return strlen(p);
}

// xexit exits the process with return code n.
void
xexit(int n)
{
        exit(n);
}

// xatexit schedules the exit-handler f to be run when the program exits.
void
xatexit(void (*f)(void))
{
        atexit(f);
}

// xprintf prints a message to standard output.
void
xprintf(char *fmt, ...)
{
        va_list arg;
        
        va_start(arg, fmt);
        vprintf(fmt, arg);
        va_end(arg);
}

// errprintf prints a message to standard output.
void
errprintf(char *fmt, ...)
{
        va_list arg;
        
        va_start(arg, fmt);
        vfprintf(stderr, fmt, arg);
        va_end(arg);
}

// xsetenv sets the environment variable $name to the given value.
void
xsetenv(char *name, char *value)
{
        setenv(name, value, 1);
}

// main takes care of OS-specific startup and dispatches to xmain.
int
main(int argc, char **argv)
{
        Buf b;
        int osx;
        struct utsname u;

        setvbuf(stdout, nil, _IOLBF, 0);
        setvbuf(stderr, nil, _IOLBF, 0);

        setenv("TERM", "dumb", 1); // disable escape codes in clang errors

        binit(&b);
        
        slash = "/";

#if defined(__APPLE__)
        gohostos = "darwin";
        // Even on 64-bit platform, darwin uname -m prints i386.
        run(&b, nil, 0, "sysctl", "machdep.cpu.extfeatures", nil);
        if(contains(bstr(&b), "EM64T"))
                gohostarch = "amd64";
#elif defined(__linux__)
        gohostos = "linux";
#elif defined(__DragonFly__)
        gohostos = "dragonfly";
#elif defined(__FreeBSD__)
        gohostos = "freebsd";
#elif defined(__FreeBSD_kernel__)
        // detect debian/kFreeBSD. 
        // http://wiki.debian.org/Debian_GNU/kFreeBSD_FAQ#Q._How_do_I_detect_kfreebsd_with_preprocessor_directives_in_a_C_program.3F
        gohostos = "freebsd";   
#elif defined(__OpenBSD__)
        gohostos = "openbsd";
#elif defined(__NetBSD__)
        gohostos = "netbsd";
#elif defined(__sun) && defined(__SVR4)
        gohostos = "solaris";
        // Even on 64-bit platform, solaris uname -m prints i86pc.
        run(&b, nil, 0, "isainfo", "-n", nil);
        if(contains(bstr(&b), "amd64"))
                gohostarch = "amd64";
        if(contains(bstr(&b), "i386"))
                gohostarch = "386";
#else
        fatal("unknown operating system");
#endif

        if(gohostarch == nil) {
                if(uname(&u) < 0)
                        fatal("uname: %s", strerror(errno));
                if(contains(u.machine, "x86_64") || contains(u.machine, "amd64"))
                        gohostarch = "amd64";
                else if(hassuffix(u.machine, "86"))
                        gohostarch = "386";
                else if(contains(u.machine, "arm"))
                        gohostarch = "arm";
                else
                        fatal("unknown architecture: %s", u.machine);
        }

        if(strcmp(gohostarch, "arm") == 0)
                maxnbg = 1;

        // The OS X 10.6 linker does not support external linking mode.
        // See golang.org/issue/5130.
        //
        // OS X 10.6 does not work with clang either, but OS X 10.9 requires it.
        // It seems to work with OS X 10.8, so we default to clang for 10.8 and later.
        // See golang.org/issue/5822.
        //
        // Roughly, OS X 10.N shows up as uname release (N+4),
        // so OS X 10.6 is uname version 10 and OS X 10.8 is uname version 12.
        if(strcmp(gohostos, "darwin") == 0) {
                if(uname(&u) < 0)
                        fatal("uname: %s", strerror(errno));
                osx = atoi(u.release) - 4;
                if(osx <= 6)
                        goextlinkenabled = "0";
                if(osx >= 8)
                        defaultclang = 1;
        }

        init();
        xmain(argc, argv);
        bfree(&b);
        return 0;
}

// xqsort is a wrapper for the C standard qsort.
void
xqsort(void *data, int n, int elemsize, int (*cmp)(const void*, const void*))
{
        qsort(data, n, elemsize, cmp);
}

// xstrcmp compares the NUL-terminated strings a and b.
int
xstrcmp(char *a, char *b)
{
        return strcmp(a, b);
}

// xstrstr returns a pointer to the first occurrence of b in a.
char*
xstrstr(char *a, char *b)
{
        return strstr(a, b);
}

// xstrrchr returns a pointer to the final occurrence of c in p.
char*
xstrrchr(char *p, int c)
{
        return strrchr(p, c);
}

// xsamefile reports whether f1 and f2 are the same file (or dir)
int
xsamefile(char *f1, char *f2)
{
        return streq(f1, f2); // suffice for now
}

sigjmp_buf sigill_jmpbuf;
static void sigillhand(int);

// xtryexecfunc tries to execute function f, if any illegal instruction
// signal received in the course of executing that function, it will
// return 0, otherwise it will return 1.
// Some systems (notably NetBSD) will spin and spin when executing VFPv3
// instructions on VFPv2 system (e.g. Raspberry Pi) without ever triggering
// SIGILL, so we set a 1-second alarm to catch that case.
int
xtryexecfunc(void (*f)(void))
{
        int r;
        r = 0;
        signal(SIGILL, sigillhand);
        signal(SIGALRM, sigillhand);
        alarm(1);
        if(sigsetjmp(sigill_jmpbuf, 1) == 0) {
                f();
                r = 1;
        }
        signal(SIGILL, SIG_DFL);
        alarm(0);
        signal(SIGALRM, SIG_DFL);
        return r;
}

// SIGILL handler helper
static void
sigillhand(int signum)
{
        USED(signum);
        siglongjmp(sigill_jmpbuf, 1);
}

static void
__cpuid(int dst[4], int ax)
{
#ifdef __i386__
        // we need to avoid ebx on i386 (esp. when -fPIC).
        asm volatile(
                "mov %%ebx, %%edi\n\t"
                "cpuid\n\t"
                "xchgl %%ebx, %%edi"
                : "=a" (dst[0]), "=D" (dst[1]), "=c" (dst[2]), "=d" (dst[3])
                : "0" (ax));
#elif defined(__x86_64__)
        asm volatile("cpuid"
                : "=a" (dst[0]), "=b" (dst[1]), "=c" (dst[2]), "=d" (dst[3])
                : "0" (ax));
#else
        dst[0] = dst[1] = dst[2] = dst[3] = 0;
#endif
}

bool
cansse2(void)
{
        int info[4];
        
        __cpuid(info, 1);
        return (info[3] & (1<<26)) != 0;        // SSE2
}

#endif // PLAN9
#endif // __WINDOWS__

/* [<][>][^][v][top][bottom][index][help] */