diff options
| author | Russ Cox <rsc@golang.org> | 2015-01-07 11:37:04 -0500 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2015-01-10 19:15:37 +0000 |
| commit | ad6ee36cac122894f7ea4043289c10a50e48ac01 (patch) | |
| tree | ba42e5f60fa1d27c6ad46c6213a92bbcafa61636 /src/cmd/dist/util.go | |
| parent | ce5cb037d171273f1a5294723234be5495c9d336 (diff) | |
| download | go-git-ad6ee36cac122894f7ea4043289c10a50e48ac01.tar.gz | |
cmd/dist: remove C sources, rename some to Go files
This CL makes the next one have nice cross-file diffs.
Change-Id: I9ce897dc505dea9923be4e823bae31f4f7fa2ee2
Reviewed-on: https://go-review.googlesource.com/2471
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'src/cmd/dist/util.go')
| -rw-r--r-- | src/cmd/dist/util.go | 847 |
1 files changed, 847 insertions, 0 deletions
diff --git a/src/cmd/dist/util.go b/src/cmd/dist/util.go new file mode 100644 index 0000000000..0fd17c1509 --- /dev/null +++ b/src/cmd/dist/util.go @@ -0,0 +1,847 @@ +// 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(streq(dp->d_name, ".") || streq(dp->d_name, "..")) + 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 && streq(p+np-ns, suffix); +} + +// 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 if(contains(u.machine, "ppc64le")) + gohostarch = "ppc64le"; + else if(contains(u.machine, "ppc64")) + gohostarch = "ppc64"; + else + fatal("unknown architecture: %s", u.machine); + } + + if(streq(gohostarch, "arm")) + 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(streq(gohostos, "darwin")) { + 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__ |
