watchman.c (1,383 lines of code) (raw):
#include "watchman.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <jansson.h>
#include "bser_write.h"
#include "proto.h"
static int use_bser_encoding = 0;
static FILE* error_handle = NULL;
/* It's safe to have a small buffer here because watchman's socket name
* is guaranteed to be under 108 bytes (see sockaddr_un). The JSON only has
* sockname and version fields.
*/
#define WATCHMAN_GET_SOCKNAME_MAX 1024
void
watchman_set_error_handle(FILE* fp) {
error_handle = fp;
}
static void do_output(const char* category, const char *msg, va_list params) {
if (error_handle != NULL) {
fprintf(error_handle, "%s: ", category);
vfprintf(error_handle, msg, params);
fprintf(error_handle, "\n");
}
}
static void trace(const char *msg, ...) {
if (getenv("LIBWATCHMAN_TRACE_WATCHMAN") != NULL) {
va_list params;
va_start(params, msg);
do_output("trace", msg, params);
va_end(params);
}
}
static void warning(const char* msg, ...) {
va_list params;
va_start(params, msg);
do_output("warning", msg, params);
va_end(params);
}
static void error(const char* msg, ...) {
va_list params;
va_start(params, msg);
do_output("error", msg, params);
va_end(params);
}
static void watchman_err(struct watchman_error *error,
enum watchman_error_code code,
const char *message, ...)
__attribute__ ((format(printf, 3, 4)));
static void
watchman_err(struct watchman_error *error, enum watchman_error_code code,
const char *message, ...)
{
if (!error)
return;
va_list argptr;
va_start(argptr, message);
char c;
int len = vsnprintf(&c, 1, message, argptr);
warning(message, argptr);
va_end(argptr);
error->message = malloc(len + 1);
error->code = code;
error->err_no = errno;
va_start(argptr, message);
vsnprintf(error->message, len + 1, message, argptr);
va_end(argptr);
}
static int unix_stream_socket(void)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
return fd;
}
static int chdir_len(const char *orig, int len)
{
char *path = malloc(len + 1);
memcpy(path, orig, len);
path[len] = 0;
int r = chdir(path);
free(path);
return r;
}
struct unix_sockaddr_context {
char *orig_dir;
};
static char *getcwd_alloc(void)
{
int buflen = PATH_MAX;
char *buf = NULL;
while (1) {
buf = realloc(buf, buflen);
if (getcwd(buf, buflen)) {
break;
}
if (errno != ERANGE) {
return NULL;
}
buflen *= 2;
}
return buf;
}
static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
struct unix_sockaddr_context *ctx)
{
size_t size = strlen(path) + 1;
ctx->orig_dir = NULL;
if (size > sizeof(sa->sun_path)) {
const char *slash = strrchr(path, '/');
const char *dir;
if (!slash) {
errno = ENAMETOOLONG;
return -1;
}
dir = path;
path = slash + 1;
size = strlen(path) + 1;
if (size > sizeof(sa->sun_path)) {
errno = ENAMETOOLONG;
return -1;
}
char *cwd = getcwd_alloc();
if (!cwd) {
return -1;
}
ctx->orig_dir = cwd;
if (chdir_len(dir, slash - dir) < 0) {
free(cwd);
return -1;
}
}
memset(sa, 0, sizeof(*sa));
sa->sun_family = AF_UNIX;
memcpy(sa->sun_path, path, size);
return 0;
}
static int unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx, struct watchman_error *error)
{
int ret = 0;
if (!ctx->orig_dir) {
return 0;
}
/*
* If we fail, we have moved the cwd of the whole process, which
* could confuse calling code. But we don't want to just die, because
* libraries shouldn't do that. So we'll return an error but be
* sad about it.
*/
if (chdir(ctx->orig_dir) < 0) {
watchman_err(error, WATCHMAN_ERR_CWD,
"unable to restore original working directory");
ret = -1;
}
free(ctx->orig_dir);
return ret;
}
static int unix_stream_connect(const char *path, struct watchman_error *error)
{
struct sockaddr_un sa;
struct unix_sockaddr_context ctx;
if (unix_sockaddr_init(&sa, path, &ctx) < 0) {
return -1;
}
int fd = unix_stream_socket();
if (fd < 0) {
return -1;
}
if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
watchman_err(error, WATCHMAN_ERR_CONNECT, "Connect error %s",
strerror(errno));
if (unix_sockaddr_cleanup(&ctx, error)) {
error->code |= WATCHMAN_ERR_CWD;
}
close(fd);
return -1;
}
if (unix_sockaddr_cleanup(&ctx, error)) {
close(fd);
return -1;
}
return fd;
}
static struct watchman_connection *
watchman_sock_connect(const char *sockname, struct timeval timeout, struct watchman_error *error)
{
use_bser_encoding = getenv("LIBWATCHMAN_USE_JSON_PROTOCOL") == NULL;
trace("Using bser encoding: %s", use_bser_encoding ? "yes" : "no");
int fd;
error->message = NULL;
fd = unix_stream_connect(sockname, error);
if (fd < 0 && !error->message) {
watchman_err(error, WATCHMAN_ERR_CONNECT, "Connect error %s",
strerror(errno));
return NULL;
}
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout))) {
watchman_err(error, WATCHMAN_ERR_CONNECT, "Failed to set timeout %s",
strerror(errno));
return NULL;
}
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout))) {
watchman_err(error, WATCHMAN_ERR_CONNECT, "Failed to set timeout %s",
strerror(errno));
return NULL;
}
FILE *sockfp = fdopen(fd, "r+");
if (!sockfp) {
close(fd);
watchman_err(error, WATCHMAN_ERR_OTHER,
"Failed to connect to watchman socket %s: %s.",
sockname, strerror(errno));
return NULL;
}
setlinebuf(sockfp);
struct watchman_connection *conn = malloc(sizeof(*conn));
conn->fp = sockfp;
return conn;
}
struct watchman_popen {
int fd;
int pid;
};
#define WATCHMAN_EXEC_FAILED 241
#define WATCHMAN_EXEC_INTERNAL_ERROR 242
static const char* get_sockname_msg = "Could not run watchman get-sockname: %s";
/* Runs watchman get-sockname and returns a FILE from which the output
can be read. */
static struct watchman_popen *watchman_popen_getsockname(struct watchman_error *err)
{
int pipefd[2];
static struct watchman_popen ret = {0, 0};
if (pipe(pipefd) < 0) {
goto fail;
}
pid_t pid = fork();
if (pid < 0) {
goto fail;
} else if (pid == 0) {
if (dup2(pipefd[1], 1) < 0) {
error("Could not dup pipe");
exit(WATCHMAN_EXEC_INTERNAL_ERROR);
}
int devnull_fh = open("/dev/null", O_RDWR);
if (devnull_fh < 0) {
error("Could not open /dev/null");
exit(WATCHMAN_EXEC_INTERNAL_ERROR);
}
if (dup2(devnull_fh, 2) < 0) {
error("Could not dup2 /dev/null");
exit(WATCHMAN_EXEC_INTERNAL_ERROR);
}
execlp("watchman", "watchman", "get-sockname", (char *) NULL);
error("execlp failed");
exit(WATCHMAN_EXEC_FAILED);
} else {
close(pipefd[1]);
ret.fd = pipefd[0];
ret.pid = pid;
return &ret;
}
fail:
watchman_err(err, WATCHMAN_ERR_OTHER, get_sockname_msg, strerror(errno));
return NULL;
}
int watchman_pclose(struct watchman_error *error, struct watchman_popen *popen)
{
close(popen->fd);
int status;
int pid = waitpid(popen->pid, &status, 0);
if (pid < 0) {
watchman_err(error, WATCHMAN_ERR_RUN_WATCHMAN, get_sockname_msg,
strerror(errno));
return -1;
}
switch(WEXITSTATUS(status)) {
case 0:
return 0;
case WATCHMAN_EXEC_FAILED:
watchman_err(error, WATCHMAN_ERR_RUN_WATCHMAN, get_sockname_msg,
strerror(errno));
return -1;
case WATCHMAN_EXEC_INTERNAL_ERROR:
watchman_err(error, WATCHMAN_ERR_OTHER, get_sockname_msg,
strerror(errno));
return -1;
default:
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN, get_sockname_msg,
strerror(errno));
return -1;
}
}
/**
* Calls select() on the given fd until it's ready to read from or
* until the timeout expires. Updates timeout to indicate the
* remaining time. Returns 1 if the socket is readable, 0 if the
* socket is not readable, and -1 on error.
*/
static int block_on_read(int fd, struct timeval *timeout)
{
struct timeval now, start, elapsed, orig_timeout;
fd_set fds;
int ret = 0;
FD_ZERO(&fds);
FD_SET(fd, &fds);
if (timeout != NULL) {
gettimeofday(&start, NULL);
memcpy(&orig_timeout, timeout, sizeof(orig_timeout));
}
ret = select(fd + 1, &fds, NULL, NULL, timeout);
if (timeout != NULL) {
gettimeofday(&now, NULL);
timersub(&now, &start, &elapsed);
timersub(&orig_timeout, &elapsed, timeout);
}
return ret;
}
/*
* Read from fd into buf until either `bytes` bytes have been read, or
* EOF, or the data that has been read can be parsed as JSON. In the
* event of a timeout or read error, returns NULL.
*/
static proto_t read_with_timeout(int fd, char* buf, size_t bytes, struct timeval timeout)
{
size_t read_so_far = 0;
ssize_t bytes_read = 0;
while (read_so_far < bytes) {
if (timeout.tv_sec || timeout.tv_usec) {
if (timeout.tv_sec < 0 || timeout.tv_usec < 0) {
/* Timeout */
return proto_null();
}
if (1 == block_on_read(fd, &timeout)) {
bytes_read = read(fd, buf, bytes - read_so_far);
} else {
return proto_null(); /* timeout or error */
}
} else {
bytes_read = read(fd, buf, bytes - read_so_far);
}
if (bytes_read < 0) {
return proto_null();
}
if (bytes_read == 0) {
/* EOF, but we couldn't parse the JSON we have so far */
return proto_null();
}
read_so_far += bytes_read;
/* try to parse this */
buf[read_so_far] = 0;
proto_t proto;
if (buf[0] <= 0x0b) {
bser_t* bser = bser_parse_buffer((uint8_t*)buf, read_so_far, NULL);
proto = proto_from_bser(bser);
} else {
json_error_t jerror;
json_t* json = json_loads(buf, JSON_DISABLE_EOF_CHECK, &jerror);
proto = proto_from_json(json);
}
return proto;
}
return proto_null();
}
/*
* Connect to watchman's socket. Sets a socket send and receive
* timeout of `timeout`. Pass a {0} for no-timeout. On error,
* returns NULL and, if `error` is non-NULL, fills it in.
*/
struct watchman_connection *
watchman_connect(struct timeval timeout, struct watchman_error *error)
{
struct watchman_connection *conn = NULL;
/* If an environment variable WATCHMAN_SOCK is set, establish a connection
to that address. Otherwise, run `watchman get-sockname` to start the
daemon and retrieve its address. */
const char *sockname_env = getenv("WATCHMAN_SOCK");
if (sockname_env) {
conn = watchman_sock_connect(sockname_env, timeout, error);
goto done;
}
struct watchman_popen *p = watchman_popen_getsockname(error);
if (p == NULL) {
return NULL;
}
char buf[WATCHMAN_GET_SOCKNAME_MAX + 1];
proto_t proto = read_with_timeout(p->fd, buf, WATCHMAN_GET_SOCKNAME_MAX, timeout);
if (watchman_pclose(error, p)) {
goto done;
}
if (proto_is_null(proto)) {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Got bad or no JSON/BSER from watchman get-sockname");
goto done;
}
if (!proto_is_object(proto)) {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Got bad JSON/BSER from watchman get-sockname: object expected");
goto bad_proto;
}
proto_t sockname_obj = proto_object_get(proto, "sockname");
if (proto_is_null(sockname_obj)) {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Got bad JSON/BSER from watchman get-sockname: "
"sockname element expected");
goto bad_proto;
}
if (!proto_is_string(sockname_obj)) {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Got bad JSON/BSER from watchman get-sockname:"
" sockname is not string");
goto bad_proto;
}
const char *sockname = proto_strdup(sockname_obj);
conn = watchman_sock_connect(sockname, timeout, error);
bad_proto:
proto_free(proto);
done:
return conn;
}
static int
watchman_send_simple_command(struct watchman_connection *conn,
struct watchman_error *error, ...)
{
int result = 0;
json_t *cmd_array = json_array();
va_list argptr;
va_start(argptr, error);
char *arg;
while ((arg = va_arg(argptr, char *))) {
json_array_append_new(cmd_array, json_string(arg));
}
if (use_bser_encoding) {
result = bser_write_to_file(cmd_array, conn->fp) == 0;
} else {
result = json_dumpf(cmd_array, conn->fp, JSON_COMPACT);
fputc('\n', conn->fp);
}
if (result) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
watchman_err(error, WATCHMAN_ERR_TIMEOUT,
"Timeout sending simple watchman command");
} else {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Failed to send simple watchman command");
}
result = 1;
}
json_decref(cmd_array);
return result;
}
static proto_t
watchman_read_with_timeout(struct watchman_connection *conn, struct timeval *timeout, struct watchman_error *error)
{
proto_t result;
json_error_t jerror;
int ret = 1;
if (!timeout || timeout->tv_sec || timeout->tv_usec)
ret = block_on_read(fileno(conn->fp), timeout);
if (ret == -1) {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Error encountered blocking on watchman");
return proto_null();
}
if (ret != 1) {
watchman_err(error, WATCHMAN_ERR_TIMEOUT,
"timed out waiting for watchman");
return proto_null();
}
if (use_bser_encoding) {
bser_t* bser = bser_parse_from_file(conn->fp, NULL);
result = proto_from_bser(bser);
} else {
json_t* json = json_loadf(conn->fp, JSON_DISABLE_EOF_CHECK, &jerror);
result = proto_from_json(json);
if (fgetc(conn->fp) != '\n') {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
watchman_err(error, WATCHMAN_ERR_TIMEOUT,
"Timeout reading EOL from watchman");
} else {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"No newline at end of reply");
}
json_decref(json);
return proto_null();
}
}
if (proto_is_null(result)) {
if (errno == EAGAIN) {
watchman_err(error, WATCHMAN_ERR_TIMEOUT,
"Timeout:EAGAIN reading from watchman.");
}
else if (errno == EWOULDBLOCK) {
watchman_err(error, WATCHMAN_ERR_TIMEOUT,
"Timeout:EWOULDBLOCK reading from watchman");
} else {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Can't parse result from watchman: %s",
jerror.text);
}
return proto_null();
}
return result;
}
static proto_t
watchman_read(struct watchman_connection *conn, struct watchman_error *error)
{
return watchman_read_with_timeout(conn, NULL, error);
}
static int
watchman_read_and_handle_errors(struct watchman_connection *conn,
struct watchman_error *error)
{
proto_t obj = watchman_read(conn, error);
if (proto_is_null(obj)) {
return 1;
}
if (!proto_is_object(obj)) {
char *bogus_text = proto_dumps(obj, 0);
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN,
"Got non-object result from watchman : %s",
bogus_text);
free(bogus_text);
proto_free(obj);
return 1;
}
proto_t error_node = proto_object_get(obj, "error");
if (!proto_is_null(error_node)) {
watchman_err(error, WATCHMAN_ERR_OTHER,
"Got error result from watchman : %s",
proto_strdup(error_node));
proto_free(obj);
return 1;
}
proto_free(obj);
return 0;
}
int
watchman_watch(struct watchman_connection *conn,
const char *path, struct watchman_error *error)
{
if (watchman_send_simple_command(conn, error, "watch", path, NULL)) {
return 1;
}
if (watchman_read_and_handle_errors(conn, error)) {
return 1;
}
return 0;
}
int
watchman_recrawl(struct watchman_connection *conn,
const char *path, struct watchman_error *error)
{
if (watchman_send_simple_command(conn, error, "debug-recrawl", path, NULL)) {
return 1;
}
if (watchman_read_and_handle_errors(conn, error)) {
return 1;
}
return 0;
}
int
watchman_watch_del(struct watchman_connection *conn,
const char *path, struct watchman_error *error)
{
if (watchman_send_simple_command(conn, error, "watch-del", path, NULL)) {
return 1;
}
if (watchman_read_and_handle_errors(conn, error)) {
return 1;
}
return 0;
}
static struct watchman_expression *
alloc_expr(enum watchman_expression_type ty)
{
struct watchman_expression *expr;
expr = calloc(1, sizeof(*expr));
expr->ty = ty;
return expr;
}
struct watchman_expression *
watchman_since_expression(const char *since, enum watchman_clockspec spec)
{
assert(since);
struct watchman_expression *expr = alloc_expr(WATCHMAN_EXPR_TY_SINCE);
expr->e.since_expr.is_str = 1;
expr->e.since_expr.t.since = strdup(since);
expr->e.since_expr.clockspec = spec;
return expr;
}
struct watchman_expression *
watchman_since_expression_time_t(time_t time, enum watchman_clockspec
spec)
{
struct watchman_expression *expr = alloc_expr(WATCHMAN_EXPR_TY_SINCE);
expr->e.since_expr.is_str = 0;
expr->e.since_expr.t.time = time;
expr->e.since_expr.clockspec = spec;
return expr;
}
/* corresponds to enum watchman_expression_type */
static char *ty_str[] = {
"allof",
"anyof",
"not",
"true",
"false",
"since",
"suffix",
"match",
"imatch",
"pcre",
"ipcre",
"name",
"iname",
"type",
"empty",
"exists"
};
/* corresponds to enum watchman_clockspec */
static char *clockspec_str[] = {
NULL,
"oclock",
"cclock",
"mtime",
"ctime"
};
/* corresponds to enum watchman_basename */
static char *basename_str[] = {
NULL,
"basename",
"wholename"
};
static json_t *
json_string_from_char(char c)
{
char str[2] = { c, 0 };
return json_string(str);
}
static json_t *
json_string_or_array(int nr, char **items)
{
if (nr == 1) {
return json_string(items[0]);
}
json_t *result = json_array();
int i;
for (i = 0; i < nr; ++i) {
json_array_append_new(result, json_string(items[i]));
}
return result;
}
static void
since_to_json(json_t *result, const struct watchman_expression *expr)
{
if (expr->e.since_expr.is_str) {
json_array_append_new(result, json_string(expr->e.since_expr.t.since));
} else {
json_array_append_new(result, json_integer(expr->e.since_expr.t.time));
}
if (expr->e.since_expr.clockspec) {
char *clockspec = clockspec_str[expr->e.since_expr.clockspec];
json_array_append_new(result, json_string(clockspec));
}
}
static json_t *
to_json(const struct watchman_expression *expr)
{
json_t *result = json_array();
json_t *arg;
json_array_append_new(result, json_string(ty_str[expr->ty]));
int i;
switch (expr->ty) {
case WATCHMAN_EXPR_TY_ALLOF:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_ANYOF:
for (i = 0; i < expr->e.union_expr.nr; ++i) {
json_array_append_new(result,
to_json(expr->e.union_expr.clauses[i]));
}
break;
case WATCHMAN_EXPR_TY_NOT:
json_array_append_new(result, to_json(expr->e.not_expr.clause));
break;
case WATCHMAN_EXPR_TY_TRUE:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_FALSE:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_EMPTY:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_EXISTS:
/* Nothing to do */
break;
case WATCHMAN_EXPR_TY_SINCE:
since_to_json(result, expr);
break;
case WATCHMAN_EXPR_TY_SUFFIX:
json_array_append_new(result,
json_string(expr->e.suffix_expr.suffix));
break;
case WATCHMAN_EXPR_TY_MATCH:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_IMATCH:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_PCRE:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_IPCRE:
json_array_append_new(result,
json_string(expr->e.match_expr.match));
if (expr->e.match_expr.basename) {
char *base = basename_str[expr->e.match_expr.basename];
json_array_append_new(result, json_string(base));
}
break;
case WATCHMAN_EXPR_TY_NAME:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_INAME:
arg =
json_string_or_array(expr->e.name_expr.nr,
expr->e.name_expr.names);
json_array_append_new(result, arg);
if (expr->e.name_expr.basename) {
char *base = basename_str[expr->e.name_expr.basename];
json_array_append_new(result, json_string(base));
}
break;
case WATCHMAN_EXPR_TY_TYPE:
json_array_append_new(result,
json_string_from_char(expr->e.
type_expr.type));
}
return result;
}
/* corresponds to enum watchman_fields */
static char *fields_str[] = {
"name",
"exists",
"cclock",
"oclock",
"ctime",
"ctime_ms",
"ctime_us",
"ctime_ns",
"ctime_f",
"mtime",
"mtime_ms",
"mtime_us",
"mtime_ns",
"mtime_f",
"size",
"uid",
"gid",
"ino",
"dev",
"nlink",
"new",
"mode"
};
json_t *
fields_to_json(int fields)
{
json_t *result = json_array();
int i = 0;
int mask;
for (mask = 1; mask < WATCHMAN_FIELD_END; mask *= 2) {
if (fields & mask) {
json_array_append_new(result, json_string(fields_str[i]));
}
++i;
}
return result;
}
#define PROTO_ASSERT(cond, condarg, msg) \
if (!cond(condarg)) { \
char *dump = proto_dumps(condarg, 0); \
watchman_err(error, WATCHMAN_ERR_WATCHMAN_BROKEN, msg, dump); \
free(dump); \
goto done; \
}
struct watchman_watch_list *
watchman_watch_list(struct watchman_connection *conn,
struct watchman_error *error)
{
struct watchman_watch_list *res = NULL;
struct watchman_watch_list *result = NULL;
if (watchman_send_simple_command(conn, error, "watch-list", NULL)) {
return NULL;
}
proto_t obj = watchman_read(conn, error);
if (proto_is_null(obj)) {
return NULL;
}
PROTO_ASSERT(proto_is_object, obj, "Got bogus value from watch-list %s");
proto_t roots = proto_object_get(obj, "roots");
PROTO_ASSERT(proto_is_array, roots, "Got bogus value from watch-list %s");
res = malloc(sizeof(*res));
int nr = proto_array_size(roots);
res->nr = 0;
res->roots = calloc(nr, sizeof(*res->roots));
int i;
for (i = 0; i < nr; ++i) {
proto_t root = proto_array_get(roots, i);
PROTO_ASSERT(proto_is_string, root,
"Got non-string root from watch-list %s");
res->nr++;
res->roots[i] = proto_strdup(root);
}
result = res;
res = NULL;
done:
if (res) {
watchman_free_watch_list(res);
}
proto_free(obj);
return result;
}
#define WRITE_BOOL_STAT(stat, statobj, attr) \
proto_t attr = proto_object_get(statobj, #attr); \
if (!proto_is_null(attr)) { \
PROTO_ASSERT(proto_is_boolean, attr, #attr " is not boolean: %s"); \
stat->attr = proto_is_true(attr); \
}
#define WRITE_INT_STAT(stat, statobj, attr) \
proto_t attr = proto_object_get(statobj, #attr); \
if (!proto_is_null(attr)) { \
PROTO_ASSERT(proto_is_integer, attr, #attr " is not an int: %s"); \
stat->attr = proto_integer_value(attr); \
}
#define WRITE_STR_STAT(stat, statobj, attr) \
proto_t attr = proto_object_get(statobj, #attr); \
if (!proto_is_null(attr)) { \
PROTO_ASSERT(proto_is_string, attr, #attr " is not a string: %s"); \
stat->attr = proto_strdup(attr); \
}
#define WRITE_FLOAT_STAT(stat, statobj, attr) \
proto_t attr = proto_object_get(statobj, #attr); \
if (!proto_is_null(attr)) { \
PROTO_ASSERT(proto_is_real, attr, #attr " is not a float: %s"); \
stat->attr = proto_real_value(attr); \
}
static int
watchman_send(struct watchman_connection *conn,
json_t *query, struct watchman_error *error)
{
int result;
if (use_bser_encoding) {
result = bser_write_to_file(query, conn->fp) == 0;
} else {
result = json_dumpf(query, conn->fp, JSON_COMPACT);
fputc('\n', conn->fp);
}
if (result) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
watchman_err(error, WATCHMAN_ERR_TIMEOUT,
"Timeout sending to watchman");
} else {
char *dump = json_dumps(query, 0);
watchman_err(error, WATCHMAN_ERR_OTHER,
"Failed to send watchman query %s", dump);
free(dump);
}
return 1;
}
return 0;
}
char *
watchman_clock(struct watchman_connection *conn,
const char *path,
unsigned int sync_timeout,
struct watchman_error *error)
{
char *result = NULL;
json_t *query = json_array();
json_array_append_new(query, json_string("clock"));
json_array_append_new(query, json_string(path));
if (sync_timeout) {
json_t *options = json_object();
json_object_set_new(options, "sync_timeout", json_integer(sync_timeout));
json_array_append_new(query, options);
}
int ret = watchman_send(conn, query, error);
json_decref(query);
if (ret) {
return NULL;
}
proto_t obj = watchman_read(conn, error);
if (proto_is_null(obj)) {
return NULL;
}
PROTO_ASSERT(proto_is_object, obj, "Got bogus value from clock %s");
proto_t clock = proto_object_get(obj, "clock");
PROTO_ASSERT(proto_is_string, clock, "Bad clock %s");
result = proto_strdup(clock);
done:
proto_free(obj);
return result;
}
static struct watchman_query_result *
watchman_query_json(struct watchman_connection *conn,
json_t *query,
struct timeval *timeout,
struct watchman_error *error)
{
struct watchman_query_result *result = NULL;
struct watchman_query_result *res = NULL;
if (watchman_send(conn, query, error)) {
return NULL;
}
/* parse the result */
proto_t obj = watchman_read_with_timeout(conn, timeout, error);
if (proto_is_null(obj)) {
return NULL;
}
PROTO_ASSERT(proto_is_object, obj, "Failed to send watchman query %s");
proto_t jerror = proto_object_get(obj, "error");
if (!proto_is_null(jerror)) {
watchman_err(error, WATCHMAN_ERR_WATCHMAN_REPORTED,
"Error result from watchman: %s",
proto_strdup(jerror));
goto done;
}
res = calloc(1, sizeof(*res));
proto_t files = proto_object_get(obj, "files");
PROTO_ASSERT(proto_is_array, files, "Bad files %s");
int nr = proto_array_size(files);
res->stats = calloc(nr, sizeof(*res->stats));
int i;
for (i = 0; i < nr; ++i) {
struct watchman_stat *stat = res->stats + i;
proto_t statobj = proto_array_get(files, i);
if (proto_is_string(statobj)) {
/* then hopefully we only requested names */
stat->name = proto_strdup(statobj);
res->nr++;
continue;
}
PROTO_ASSERT(proto_is_object, statobj, "must be object: %s");
proto_t name = proto_object_get(statobj, "name");
PROTO_ASSERT(proto_is_string, name, "name must be string: %s");
stat->name = proto_strdup(name);
WRITE_BOOL_STAT(stat, statobj, exists);
WRITE_INT_STAT(stat, statobj, ctime);
WRITE_INT_STAT(stat, statobj, ctime_ms);
WRITE_INT_STAT(stat, statobj, ctime_us);
WRITE_INT_STAT(stat, statobj, ctime_ns);
WRITE_INT_STAT(stat, statobj, dev);
WRITE_INT_STAT(stat, statobj, gid);
WRITE_INT_STAT(stat, statobj, ino);
WRITE_INT_STAT(stat, statobj, mode);
WRITE_INT_STAT(stat, statobj, mtime);
WRITE_INT_STAT(stat, statobj, mtime_ms);
WRITE_INT_STAT(stat, statobj, mtime_us);
WRITE_INT_STAT(stat, statobj, mtime_ns);
WRITE_INT_STAT(stat, statobj, nlink);
WRITE_INT_STAT(stat, statobj, size);
WRITE_INT_STAT(stat, statobj, uid);
WRITE_STR_STAT(stat, statobj, cclock);
WRITE_STR_STAT(stat, statobj, oclock);
WRITE_FLOAT_STAT(stat, statobj, ctime_f);
WRITE_FLOAT_STAT(stat, statobj, mtime_f);
/* the one we have to do manually because we don't
* want to use the name "new" */
proto_t newer = proto_object_get(statobj, "new");
if (!proto_is_null(newer)) {
stat->newer = proto_is_true(newer);
}
res->nr++;
}
proto_t version = proto_object_get(obj, "version");
PROTO_ASSERT(proto_is_string, version, "Bad version %s");
res->version = proto_strdup(version);
proto_t clock = proto_object_get(obj, "clock");
PROTO_ASSERT(proto_is_string, clock, "Bad clock %s");
res->clock = proto_strdup(clock);
proto_t fresh = proto_object_get(obj, "is_fresh_instance");
PROTO_ASSERT(proto_is_boolean, fresh, "Bad is_fresh_instance %s");
res->is_fresh_instance = proto_is_true(fresh);
result = res;
res = NULL;
done:
if (res) {
watchman_free_query_result(res);
}
proto_free(obj);
return result;
}
struct watchman_query *
watchman_query(void)
{
struct watchman_query *result = calloc(1, sizeof(*result));
result->sync_timeout = -1;
return result;
}
void
watchman_free_query(struct watchman_query *query)
{
if (query->since_is_str) {
free(query->s.str);
query->s.str = NULL;
}
if (query->nr_suffixes) {
int i;
for (i = 0; i < query->nr_suffixes; ++i) {
free(query->suffixes[i]);
query->suffixes[i] = NULL;
}
free(query->suffixes);
query->suffixes = NULL;
}
if (query->nr_paths) {
int i;
for (i = 0; i < query->nr_paths; ++i) {
free(query->paths[i].path);
query->paths[i].path = NULL;
}
free(query->paths);
query->paths = NULL;
}
free(query);
}
void
watchman_query_add_suffix(struct watchman_query *query, const char *suffix)
{
assert(suffix);
if (query->cap_suffixes == query->nr_suffixes) {
if (query->nr_suffixes == 0) {
query->cap_suffixes = 10;
} else {
query->cap_suffixes *= 2;
}
int new_size = sizeof(*query->suffixes) * query->cap_suffixes;
query->suffixes = realloc(query->suffixes, new_size);
}
query->suffixes[query->nr_suffixes] = strdup(suffix);
query->nr_suffixes++;
}
void
watchman_query_add_path(struct watchman_query *query, const char *path, int depth)
{
if (query->cap_paths == query->nr_paths) {
if (query->nr_paths == 0) {
query->cap_paths = 10;
} else {
query->cap_paths *= 2;
}
int new_size = sizeof(*query->paths) * query->cap_paths;
query->paths = realloc(query->paths, new_size);
}
query->paths[query->nr_paths].path = strdup(path);
query->paths[query->nr_paths].depth = depth;
query->nr_paths++;
}
void
watchman_query_set_since_oclock(struct watchman_query *query, const char *since)
{
if (query->since_is_str) {
free(query->s.str);
}
query->since_is_str = 1;
query->s.str = strdup(since);
}
void
watchman_query_set_since_time_t(struct watchman_query *query, time_t since)
{
if (query->since_is_str) {
free(query->s.str);
}
query->since_is_str = 1;
query->s.time = since;
}
void
watchman_query_set_fields(struct watchman_query *query, int fields)
{
query->fields = fields;
}
void
watchman_query_set_empty_on_fresh(struct watchman_query *query,
bool empty_on_fresh)
{
query->empty_on_fresh = empty_on_fresh;
}
static json_t *
json_path(struct watchman_pathspec *spec)
{
if (spec->depth == -1) {
return json_string(spec->path);
}
json_t *obj = json_object();
json_object_set_new(obj, "depth", json_integer(spec->depth));
json_object_set_new(obj, "path", json_string(spec->path));
return obj;
}
struct watchman_query_result *
watchman_do_query_timeout(struct watchman_connection *conn,
const char *fs_path,
const struct watchman_query *query,
const struct watchman_expression *expr,
struct timeval *timeout,
struct watchman_error *error)
{
/* construct the json */
json_t *json = json_array();
json_array_append_new(json, json_string("query"));
json_array_append_new(json, json_string(fs_path));
json_t *obj = json_object();
json_object_set_new(obj, "expression", to_json(expr));
if (query) {
if (query->fields) {
json_object_set_new(obj, "fields", fields_to_json(query->fields));
}
if (query->empty_on_fresh) {
json_object_set_new(obj, "empty_on_fresh_instance",
json_true());
}
if (query->s.time) {
if (query->since_is_str) {
json_object_set_new(obj, "since", json_string(query->s.str));
} else {
json_t *since = json_integer(query->s.time);
json_object_set_new(obj, "since", since);
}
}
if (query->nr_suffixes) {
/* Note that even if you have only one suffix,
* watchman requires this to be an array. */
int i;
json_t *suffixes = json_array();
for (i = 0; i < query->nr_suffixes; ++i) {
json_array_append_new(suffixes,
json_string(query->suffixes[i]));
}
json_object_set_new(obj, "suffix", suffixes);
}
if (query->nr_paths) {
int i;
json_t *paths = json_array();
for (i = 0; i < query->nr_paths; ++i) {
json_array_append_new(paths, json_path(&query->paths[i]));
}
json_object_set_new(obj, "path", paths);
}
if (query->all) {
json_object_set_new(obj, "all", json_string("all"));
}
if (query->sync_timeout >= 0) {
json_object_set_new(obj, "sync_timeout",
json_integer(query->sync_timeout));
}
}
json_array_append_new(json, obj);
/* do the query */
struct watchman_query_result *r = watchman_query_json(conn, json, timeout, error);
json_decref(json);
return r;
}
struct watchman_query_result *
watchman_do_query(struct watchman_connection *conn,
const char *fs_path,
const struct watchman_query *query,
const struct watchman_expression *expr,
struct watchman_error *error)
{
struct timeval unused = {0};
return watchman_do_query_timeout(conn, fs_path, query, expr, &unused, error);
}
void
watchman_free_expression(struct watchman_expression *expr)
{
int i;
switch (expr->ty) {
case WATCHMAN_EXPR_TY_ALLOF:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_ANYOF:
for (i = 0; i < expr->e.union_expr.nr; ++i) {
watchman_free_expression(expr->e.union_expr.clauses[i]);
}
free(expr->e.union_expr.clauses);
free(expr);
break;
case WATCHMAN_EXPR_TY_NOT:
watchman_free_expression(expr->e.not_expr.clause);
free(expr);
break;
case WATCHMAN_EXPR_TY_TRUE:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_FALSE:
/*-fallthrough*/
/* These are singletons; don't delete them */
break;
case WATCHMAN_EXPR_TY_SINCE:
if (expr->e.since_expr.is_str) {
free(expr->e.since_expr.t.since);
}
free(expr);
break;
case WATCHMAN_EXPR_TY_SUFFIX:
free(expr->e.suffix_expr.suffix);
free(expr);
break;
case WATCHMAN_EXPR_TY_MATCH:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_IMATCH:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_PCRE:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_IPCRE:
/*-fallthrough*/
free(expr->e.match_expr.match);
free(expr);
break;
case WATCHMAN_EXPR_TY_NAME:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_INAME:
for (i = 0; i < expr->e.name_expr.nr; ++i) {
free(expr->e.name_expr.names[i]);
}
free(expr->e.name_expr.names);
free(expr);
break;
case WATCHMAN_EXPR_TY_TYPE:
free(expr);
break;
case WATCHMAN_EXPR_TY_EMPTY:
/*-fallthrough*/
case WATCHMAN_EXPR_TY_EXISTS:
/* These are singletons; don't delete them */
break;
}
}
void
watchman_connection_close(struct watchman_connection *conn)
{
if (!conn->fp) {
return;
}
fclose(conn->fp);
conn->fp = NULL;
free(conn);
}
void
watchman_release_error(struct watchman_error *error)
{
if (error->message) {
free(error->message);
}
}
void
watchman_free_watch_list(struct watchman_watch_list *list)
{
int i;
for (i = 0; i < list->nr; ++i) {
free(list->roots[i]);
list->roots[i] = NULL;
}
free(list->roots);
list->roots = NULL;
free(list);
}
/* Not a _free_ function, since stats are allocated as a block. */
static void
watchman_release_stat(struct watchman_stat *stat)
{
if (stat->name) {
free(stat->name);
stat->name = NULL;
}
}
void
watchman_free_query_result(struct watchman_query_result *result)
{
if (result->version) {
free(result->version);
result->version = NULL;
}
if (result->clock) {
free(result->clock);
result->clock = NULL;
}
if (result->stats) {
int i;
for (i = 0; i < result->nr; ++i) {
watchman_release_stat(&(result->stats[i]));
}
free(result->stats);
result->stats = NULL;
}
free(result);
}
struct watchman_expression *
watchman_not_expression(struct watchman_expression *expression)
{
struct watchman_expression *not_expr = alloc_expr(WATCHMAN_EXPR_TY_NOT);
not_expr->e.not_expr.clause = expression;
return not_expr;
}
static struct watchman_expression *
watchman_union_expression(enum watchman_expression_type
ty, int nr, struct watchman_expression **expressions)
{
assert(nr);
assert(expressions);
size_t sz = sizeof(*expressions);
struct watchman_expression *result = malloc(sizeof(*result));
result->ty = ty;
result->e.union_expr.nr = nr;
result->e.union_expr.clauses = malloc(nr * sz);
memcpy(result->e.union_expr.clauses, expressions, nr * sz);
return result;
}
struct watchman_expression *
watchman_allof_expression(int nr, struct watchman_expression **expressions)
{
return watchman_union_expression(WATCHMAN_EXPR_TY_ALLOF, nr, expressions);
}
struct watchman_expression *
watchman_anyof_expression(int nr, struct watchman_expression **expressions)
{
return watchman_union_expression(WATCHMAN_EXPR_TY_ANYOF, nr, expressions);
}
#define STATIC_EXPR(ty, tylower) \
static struct watchman_expression ty##_EXPRESSION = \
{ WATCHMAN_EXPR_TY_##ty }; \
struct watchman_expression * \
watchman_##tylower##_expression(void) \
{ \
return &ty##_EXPRESSION; \
}
STATIC_EXPR(EMPTY, empty)
STATIC_EXPR(TRUE, true)
STATIC_EXPR(FALSE, false)
STATIC_EXPR(EXISTS, exists)
#undef STATIC_EXPR
struct watchman_expression *
watchman_suffix_expression(const char *suffix)
{
assert(suffix);
struct watchman_expression *expr = alloc_expr(WATCHMAN_EXPR_TY_SUFFIX);
expr->e.suffix_expr.suffix = strdup(suffix);
return expr;
}
#define MATCH_EXPR(tyupper, tylower) \
struct watchman_expression * \
watchman_##tylower##_expression(const char *match, \
enum watchman_basename basename) \
{ \
assert(match); \
struct watchman_expression *expr = \
alloc_expr(WATCHMAN_EXPR_TY_##tyupper); \
expr->e.match_expr.match = strdup(match); \
expr->e.match_expr.basename = basename; \
return expr; \
}
MATCH_EXPR(MATCH, match)
MATCH_EXPR(IMATCH, imatch)
MATCH_EXPR(PCRE, pcre)
MATCH_EXPR(IPCRE, ipcre)
#undef MATCH_EXPR
#define NAME_EXPR(tyupper, tylower) \
struct watchman_expression * \
watchman_##tylower##_expression(const char *name, \
enum watchman_basename basename) \
{ \
assert(name); \
return watchman_##tylower##s_expression(1, &name, basename); \
}
NAME_EXPR(NAME, name)
NAME_EXPR(INAME, iname)
#undef NAME_EXPR
#define NAMES_EXPR(tyupper, tylower) \
struct watchman_expression * \
watchman_##tylower##s_expression(int nr, const char **names, \
enum watchman_basename basename) \
{ \
assert(nr); \
assert(names); \
struct watchman_expression *result = \
alloc_expr(WATCHMAN_EXPR_TY_##tyupper); \
result->e.name_expr.nr = nr; \
result->e.name_expr.names = malloc(nr * sizeof(*names)); \
int i; \
for (i = 0; i < nr; ++i) { \
result->e.name_expr.names[i] = strdup(names[i]); \
} \
return result; \
}
NAMES_EXPR(NAME, name)
NAMES_EXPR(INAME, iname)
#undef NAMES_EXPR
struct watchman_expression *
watchman_type_expression(char c)
{
struct watchman_expression *result = alloc_expr(WATCHMAN_EXPR_TY_TYPE);
result->e.type_expr.type = c;
return result;
}
int
watchman_version(struct watchman_connection *conn,
struct watchman_error *error,
struct watchman_version* version)
{
const char *result = NULL;
json_t *cmd = json_array();
json_array_append_new(cmd, json_string("version"));
int ret = watchman_send(conn, cmd, error);
json_decref(cmd);
if (ret) {
return -1;
}
proto_t obj = watchman_read(conn, error);
if (proto_is_null(obj)) {
return -1;
}
PROTO_ASSERT(proto_is_object, obj, "Got bogus value from version %s");
proto_t version_field = proto_object_get(obj, "version");
PROTO_ASSERT(proto_is_string, version_field, "Bad version %s");
result = proto_strdup(version_field);
int count = sscanf(result, "%d.%d.%d", &version->major,
&version->minor, &version->micro);
proto_free(obj);
return count == 3 ? 0 : -1;
done:
proto_free(obj);
return -1;
}
int
watchman_shutdown_server(struct watchman_connection *conn,
struct watchman_error *error)
{
json_t *cmd = json_array();
json_array_append_new(cmd, json_string("shutdown-server"));
int ret = watchman_send(conn, cmd, error);
json_decref(cmd);
if (ret) {
return -1;
}
proto_t obj = watchman_read(conn, error);
if (proto_is_null(obj)) {
return -1;
}
proto_free(obj);
return 0;
}