mirror of
https://github.com/golang/go
synced 2024-10-04 07:11:21 -06:00
735 lines
12 KiB
C
735 lines
12 KiB
C
|
// 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
|
||
|
//
|
||
|
#ifdef PLAN9
|
||
|
|
||
|
#include <u.h>
|
||
|
#include <libc.h>
|
||
|
#include <stdio.h>
|
||
|
#undef nil
|
||
|
#undef nelem
|
||
|
#include "a.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");
|
||
|
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 != nil)
|
||
|
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 b1, cmd;
|
||
|
char *q;
|
||
|
|
||
|
while(nbg >= maxnbg)
|
||
|
bgwait1();
|
||
|
|
||
|
binit(&b1);
|
||
|
binit(&cmd);
|
||
|
|
||
|
if(!isabs(argv->p[0])) {
|
||
|
bpathf(&b1, "/bin/%s", argv->p[0]);
|
||
|
free(argv->p[0]);
|
||
|
argv->p[0] = xstrdup(bstr(&b1));
|
||
|
}
|
||
|
|
||
|
// Generate a copy of the command to show in a log.
|
||
|
// Substitute $WORK for the work directory.
|
||
|
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)
|
||
|
xprintf("%s\n", bstr(&cmd));
|
||
|
|
||
|
if(b != nil) {
|
||
|
breset(b);
|
||
|
if(pipe(p) < 0)
|
||
|
fatal("pipe");
|
||
|
}
|
||
|
|
||
|
switch(pid = fork()) {
|
||
|
case -1:
|
||
|
fatal("fork");
|
||
|
case 0:
|
||
|
if(b != nil) {
|
||
|
close(0);
|
||
|
close(p[0]);
|
||
|
dup(p[1], 1);
|
||
|
dup(p[1], 2);
|
||
|
if(p[1] > 2)
|
||
|
close(p[1]);
|
||
|
}
|
||
|
if(dir != nil) {
|
||
|
if(chdir(dir) < 0) {
|
||
|
fprint(2, "chdir: %r\n");
|
||
|
_exits("chdir");
|
||
|
}
|
||
|
}
|
||
|
vadd(argv, nil);
|
||
|
exec(argv->p[0], argv->p);
|
||
|
fprint(2, "%s\n", bstr(&cmd));
|
||
|
fprint(2, "exec: %r\n");
|
||
|
_exits("exec");
|
||
|
}
|
||
|
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);
|
||
|
bfree(&b1);
|
||
|
}
|
||
|
|
||
|
// bgwait1 waits for a single background job.
|
||
|
static void
|
||
|
bgwait1(void)
|
||
|
{
|
||
|
Waitmsg *w;
|
||
|
int i, mode;
|
||
|
char *cmd;
|
||
|
Buf *b;
|
||
|
|
||
|
w = wait();
|
||
|
if(w == nil)
|
||
|
fatal("wait");
|
||
|
|
||
|
for(i=0; i<nbg; i++)
|
||
|
if(bg[i].pid == w->pid)
|
||
|
goto ok;
|
||
|
fatal("wait: 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 && w->msg[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[4096];
|
||
|
|
||
|
breset(b);
|
||
|
if(getwd(buf, sizeof buf) == nil)
|
||
|
fatal("getwd");
|
||
|
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)
|
||
|
{
|
||
|
char buf[4096];
|
||
|
int fd;
|
||
|
|
||
|
fd = open(path, OREAD);
|
||
|
if(fd2path(fd, buf, sizeof buf) < 0)
|
||
|
fatal("fd2path");
|
||
|
close(fd);
|
||
|
breset(b);
|
||
|
bwritestr(b, buf);
|
||
|
}
|
||
|
|
||
|
// isdir reports whether p names an existing directory.
|
||
|
bool
|
||
|
isdir(char *p)
|
||
|
{
|
||
|
Dir *d;
|
||
|
ulong mode;
|
||
|
|
||
|
d = dirstat(p);
|
||
|
if(d == nil)
|
||
|
return 0;
|
||
|
mode = d->mode;
|
||
|
free(d);
|
||
|
return (mode & DMDIR) == DMDIR;
|
||
|
}
|
||
|
|
||
|
// isfile reports whether p names an existing file.
|
||
|
bool
|
||
|
isfile(char *p)
|
||
|
{
|
||
|
Dir *d;
|
||
|
ulong mode;
|
||
|
|
||
|
d = dirstat(p);
|
||
|
if(d == nil)
|
||
|
return 0;
|
||
|
mode = d->mode;
|
||
|
free(d);
|
||
|
return (mode & DMDIR) == 0;
|
||
|
}
|
||
|
|
||
|
// mtime returns the modification time of the file p.
|
||
|
Time
|
||
|
mtime(char *p)
|
||
|
{
|
||
|
Dir *d;
|
||
|
ulong t;
|
||
|
|
||
|
d = dirstat(p);
|
||
|
if(d == nil)
|
||
|
return 0;
|
||
|
t = d->mtime;
|
||
|
free(d);
|
||
|
return (Time)t;
|
||
|
}
|
||
|
|
||
|
// 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, OREAD);
|
||
|
if(fd < 0)
|
||
|
fatal("open %s", file);
|
||
|
breadfrom(b, fd);
|
||
|
close(fd);
|
||
|
}
|
||
|
|
||
|
// writefile writes b to the named file, creating it if needed.
|
||
|
void
|
||
|
writefile(Buf *b, char *file, int exec)
|
||
|
{
|
||
|
int fd;
|
||
|
Dir d;
|
||
|
|
||
|
fd = create(file, ORDWR, 0666);
|
||
|
if(fd < 0)
|
||
|
fatal("create %s", file);
|
||
|
if(write(fd, b->p, b->len) != b->len)
|
||
|
fatal("short write");
|
||
|
if(exec) {
|
||
|
nulldir(&d);
|
||
|
d.mode = 0755;
|
||
|
dirfwstat(fd, &d);
|
||
|
}
|
||
|
close(fd);
|
||
|
}
|
||
|
|
||
|
// xmkdir creates the directory p.
|
||
|
void
|
||
|
xmkdir(char *p)
|
||
|
{
|
||
|
int fd;
|
||
|
|
||
|
if(isdir(p))
|
||
|
return;
|
||
|
fd = create(p, OREAD, 0777|DMDIR);
|
||
|
close(fd);
|
||
|
if(fd < 0)
|
||
|
fatal("mkdir %s", p);
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
xprintf("rm %s\n", p);
|
||
|
remove(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)
|
||
|
xprintf("rm %s\n", p);
|
||
|
remove(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;
|
||
|
int fd, i, n;
|
||
|
|
||
|
vreset(dst);
|
||
|
|
||
|
fd = open(dir, OREAD);
|
||
|
if(fd < 0)
|
||
|
fatal("open %s", dir);
|
||
|
n = dirreadall(fd, &d);
|
||
|
for(i=0; i<n; i++)
|
||
|
vadd(dst, d[i].name);
|
||
|
free(d);
|
||
|
close(fd);
|
||
|
}
|
||
|
|
||
|
// xworkdir creates a new temporary directory to hold object files
|
||
|
// and returns the name of that directory.
|
||
|
char*
|
||
|
xworkdir(void)
|
||
|
{
|
||
|
Buf b;
|
||
|
char *p;
|
||
|
int fd, tries;
|
||
|
|
||
|
binit(&b);
|
||
|
|
||
|
fd = 0;
|
||
|
for(tries=0; tries<1000; tries++) {
|
||
|
bprintf(&b, "/tmp/go-cbuild-%06x", nrand((1<<24)-1));
|
||
|
fd = create(bstr(&b), OREAD|OEXCL, 0700|DMDIR);
|
||
|
if(fd >= 0)
|
||
|
goto done;
|
||
|
}
|
||
|
fatal("xworkdir create");
|
||
|
|
||
|
done:
|
||
|
close(fd);
|
||
|
p = btake(&b);
|
||
|
|
||
|
bfree(&b);
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
// fatal prints an error message to standard error and exits.
|
||
|
void
|
||
|
fatal(char *msg, ...)
|
||
|
{
|
||
|
char buf[ERRMAX];
|
||
|
va_list arg;
|
||
|
|
||
|
rerrstr(buf, sizeof buf);
|
||
|
|
||
|
fflush(stdout);
|
||
|
fprintf(stderr, "go tool dist: ");
|
||
|
va_start(arg, msg);
|
||
|
vfprintf(stderr, msg, arg);
|
||
|
va_end(arg);
|
||
|
|
||
|
if(buf[0])
|
||
|
fprintf(stderr, ": %s", buf);
|
||
|
fprintf(stderr, "\n");
|
||
|
|
||
|
bgwait();
|
||
|
exits(msg);
|
||
|
}
|
||
|
|
||
|
// 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 wtih 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)
|
||
|
{
|
||
|
char buf[32];
|
||
|
|
||
|
snprintf(buf, sizeof buf, "%d", n);
|
||
|
exits(buf);
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
// xsetenv sets the environment variable $name to the given value.
|
||
|
void
|
||
|
xsetenv(char *name, char *value)
|
||
|
{
|
||
|
putenv(name, value);
|
||
|
}
|
||
|
|
||
|
// main takes care of OS-specific startup and dispatches to xmain.
|
||
|
void
|
||
|
main(int argc, char **argv)
|
||
|
{
|
||
|
Buf b;
|
||
|
|
||
|
setvbuf(stdout, nil, _IOLBF, BUFSIZ);
|
||
|
setvbuf(stderr, nil, _IOLBF, BUFSIZ);
|
||
|
|
||
|
binit(&b);
|
||
|
|
||
|
rfork(RFENVG);
|
||
|
|
||
|
slash = "/";
|
||
|
gohostos = "plan9";
|
||
|
|
||
|
xgetenv(&b, "objtype");
|
||
|
if(b.len == 0)
|
||
|
fatal("$objtype is unset");
|
||
|
gohostarch = btake(&b);
|
||
|
|
||
|
xgetenv(&b, "GOBIN");
|
||
|
if(b.len == 0){
|
||
|
bpathf(&b, "/%s/bin", gohostarch);
|
||
|
xsetenv("GOBIN", bstr(&b));
|
||
|
}
|
||
|
|
||
|
srand(time(0)+getpid());
|
||
|
init();
|
||
|
xmain(argc, argv);
|
||
|
|
||
|
bfree(&b);
|
||
|
exits(nil);
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
#endif // PLAN9
|