|
|
/*
|
|
|
* Utility functions
|
|
|
*
|
|
|
* Copyright (c) 2011 Yuya Nishihara <yuya@tcha.org>
|
|
|
*
|
|
|
* This software may be used and distributed according to the terms of the
|
|
|
* GNU General Public License version 2 or any later version.
|
|
|
*/
|
|
|
|
|
|
#include <errno.h>
|
|
|
#include <fcntl.h>
|
|
|
#include <signal.h>
|
|
|
#include <stdarg.h>
|
|
|
#include <stdio.h>
|
|
|
#include <stdlib.h>
|
|
|
#include <string.h>
|
|
|
#include <sys/time.h>
|
|
|
#include <sys/types.h>
|
|
|
#include <sys/wait.h>
|
|
|
#include <unistd.h>
|
|
|
|
|
|
#include "util.h"
|
|
|
|
|
|
static int colorenabled = 0;
|
|
|
|
|
|
static inline void fsetcolor(FILE *fp, const char *code)
|
|
|
{
|
|
|
if (!colorenabled)
|
|
|
return;
|
|
|
fprintf(fp, "\033[%sm", code);
|
|
|
}
|
|
|
|
|
|
static void vabortmsgerrno(int no, const char *fmt, va_list args)
|
|
|
{
|
|
|
fsetcolor(stderr, "1;31");
|
|
|
fputs("chg: abort: ", stderr);
|
|
|
vfprintf(stderr, fmt, args);
|
|
|
if (no != 0)
|
|
|
fprintf(stderr, " (errno = %d, %s)", no, strerror(no));
|
|
|
fsetcolor(stderr, "");
|
|
|
fputc('\n', stderr);
|
|
|
exit(255);
|
|
|
}
|
|
|
|
|
|
void abortmsg(const char *fmt, ...)
|
|
|
{
|
|
|
va_list args;
|
|
|
va_start(args, fmt);
|
|
|
vabortmsgerrno(0, fmt, args);
|
|
|
va_end(args);
|
|
|
}
|
|
|
|
|
|
void abortmsgerrno(const char *fmt, ...)
|
|
|
{
|
|
|
int no = errno;
|
|
|
va_list args;
|
|
|
va_start(args, fmt);
|
|
|
vabortmsgerrno(no, fmt, args);
|
|
|
va_end(args);
|
|
|
}
|
|
|
|
|
|
static int debugmsgenabled = 0;
|
|
|
static double debugstart = 0;
|
|
|
|
|
|
static double now()
|
|
|
{
|
|
|
struct timeval t;
|
|
|
gettimeofday(&t, NULL);
|
|
|
return t.tv_usec / 1e6 + t.tv_sec;
|
|
|
}
|
|
|
|
|
|
void enablecolor(void)
|
|
|
{
|
|
|
colorenabled = 1;
|
|
|
}
|
|
|
|
|
|
void enabledebugmsg(void)
|
|
|
{
|
|
|
debugmsgenabled = 1;
|
|
|
debugstart = now();
|
|
|
}
|
|
|
|
|
|
void debugmsg(const char *fmt, ...)
|
|
|
{
|
|
|
if (!debugmsgenabled)
|
|
|
return;
|
|
|
|
|
|
va_list args;
|
|
|
va_start(args, fmt);
|
|
|
fsetcolor(stderr, "1;30");
|
|
|
fprintf(stderr, "chg: debug: %4.6f ", now() - debugstart);
|
|
|
vfprintf(stderr, fmt, args);
|
|
|
fsetcolor(stderr, "");
|
|
|
fputc('\n', stderr);
|
|
|
va_end(args);
|
|
|
}
|
|
|
|
|
|
void fchdirx(int dirfd)
|
|
|
{
|
|
|
int r = fchdir(dirfd);
|
|
|
if (r == -1)
|
|
|
abortmsgerrno("failed to fchdir");
|
|
|
}
|
|
|
|
|
|
void fsetcloexec(int fd)
|
|
|
{
|
|
|
int flags = fcntl(fd, F_GETFD);
|
|
|
if (flags < 0)
|
|
|
abortmsgerrno("cannot get flags of fd %d", fd);
|
|
|
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
|
|
|
abortmsgerrno("cannot set flags of fd %d", fd);
|
|
|
}
|
|
|
|
|
|
void *mallocx(size_t size)
|
|
|
{
|
|
|
void *result = malloc(size);
|
|
|
if (!result)
|
|
|
abortmsg("failed to malloc");
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
void *reallocx(void *ptr, size_t size)
|
|
|
{
|
|
|
void *result = realloc(ptr, size);
|
|
|
if (!result)
|
|
|
abortmsg("failed to realloc");
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* Execute a shell command in mostly the same manner as system(), with the
|
|
|
* give environment variables, after chdir to the given cwd. Returns a status
|
|
|
* code compatible with the Python subprocess module.
|
|
|
*/
|
|
|
int runshellcmd(const char *cmd, const char *envp[], const char *cwd)
|
|
|
{
|
|
|
enum { F_SIGINT = 1, F_SIGQUIT = 2, F_SIGMASK = 4, F_WAITPID = 8 };
|
|
|
unsigned int doneflags = 0;
|
|
|
int status = 0;
|
|
|
struct sigaction newsa, oldsaint, oldsaquit;
|
|
|
sigset_t oldmask;
|
|
|
|
|
|
/* block or mask signals just as system() does */
|
|
|
memset(&newsa, 0, sizeof(newsa));
|
|
|
newsa.sa_handler = SIG_IGN;
|
|
|
newsa.sa_flags = 0;
|
|
|
if (sigemptyset(&newsa.sa_mask) < 0)
|
|
|
goto done;
|
|
|
if (sigaction(SIGINT, &newsa, &oldsaint) < 0)
|
|
|
goto done;
|
|
|
doneflags |= F_SIGINT;
|
|
|
if (sigaction(SIGQUIT, &newsa, &oldsaquit) < 0)
|
|
|
goto done;
|
|
|
doneflags |= F_SIGQUIT;
|
|
|
|
|
|
if (sigaddset(&newsa.sa_mask, SIGCHLD) < 0)
|
|
|
goto done;
|
|
|
if (sigprocmask(SIG_BLOCK, &newsa.sa_mask, &oldmask) < 0)
|
|
|
goto done;
|
|
|
doneflags |= F_SIGMASK;
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
if (pid < 0)
|
|
|
goto done;
|
|
|
if (pid == 0) {
|
|
|
sigaction(SIGINT, &oldsaint, NULL);
|
|
|
sigaction(SIGQUIT, &oldsaquit, NULL);
|
|
|
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
|
|
if (cwd && chdir(cwd) < 0)
|
|
|
_exit(127);
|
|
|
const char *argv[] = {"sh", "-c", cmd, NULL};
|
|
|
if (envp) {
|
|
|
execve("/bin/sh", (char **)argv, (char **)envp);
|
|
|
} else {
|
|
|
execv("/bin/sh", (char **)argv);
|
|
|
}
|
|
|
_exit(127);
|
|
|
} else {
|
|
|
if (waitpid(pid, &status, 0) < 0)
|
|
|
goto done;
|
|
|
doneflags |= F_WAITPID;
|
|
|
}
|
|
|
|
|
|
done:
|
|
|
if (doneflags & F_SIGINT)
|
|
|
sigaction(SIGINT, &oldsaint, NULL);
|
|
|
if (doneflags & F_SIGQUIT)
|
|
|
sigaction(SIGQUIT, &oldsaquit, NULL);
|
|
|
if (doneflags & F_SIGMASK)
|
|
|
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
|
|
|
|
|
/* no way to report other errors, use 127 (= shell termination) */
|
|
|
if (!(doneflags & F_WAITPID))
|
|
|
return 127;
|
|
|
if (WIFEXITED(status))
|
|
|
return WEXITSTATUS(status);
|
|
|
if (WIFSIGNALED(status))
|
|
|
return -WTERMSIG(status);
|
|
|
return 127;
|
|
|
}
|
|
|
|