freebsd-nq/contrib/csup/proto.c
Ulf Lilleengen d1ec640bcf - Do not free the pattern lists immediately after use, as they might be needed
again in case the connection is interrupted and csup have to reconnect. The
  lists will be freed after the collection has been completely processed.

PR:		bin/131477
Tested by:	dchagin
2009-02-15 13:22:21 +00:00

1048 lines
25 KiB
C

/*-
* Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <netdb.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "detailer.h"
#include "fattr.h"
#include "fixups.h"
#include "globtree.h"
#include "keyword.h"
#include "lister.h"
#include "misc.h"
#include "mux.h"
#include "proto.h"
#include "queue.h"
#include "stream.h"
#include "threads.h"
#include "updater.h"
struct killer {
pthread_t thread;
sigset_t sigset;
struct mux *mux;
int killedby;
};
static void killer_start(struct killer *, struct mux *);
static void *killer_run(void *);
static void killer_stop(struct killer *);
static int proto_waitconnect(int);
static int proto_greet(struct config *);
static int proto_negproto(struct config *);
static int proto_login(struct config *);
static int proto_fileattr(struct config *);
static int proto_xchgcoll(struct config *);
static struct mux *proto_mux(struct config *);
static int proto_escape(struct stream *, const char *);
static void proto_unescape(char *);
static int
proto_waitconnect(int s)
{
fd_set readfd;
socklen_t len;
int error, rv, soerror;
FD_ZERO(&readfd);
FD_SET(s, &readfd);
do {
rv = select(s + 1, &readfd, NULL, NULL, NULL);
} while (rv == -1 && errno == EINTR);
if (rv == -1)
return (-1);
/* Check that the connection was really successful. */
len = sizeof(soerror);
error = getsockopt(s, SOL_SOCKET, SO_ERROR, &soerror, &len);
if (error) {
/* We have no choice but faking an error here. */
errno = ECONNREFUSED;
return (-1);
}
if (soerror) {
errno = soerror;
return (-1);
}
return (0);
}
/* Connect to the CVSup server. */
int
proto_connect(struct config *config, int family, uint16_t port)
{
char addrbuf[NI_MAXHOST];
/* Enough to hold sizeof("cvsup") or any port number. */
char servname[8];
struct addrinfo *res, *ai, hints;
int error, opt, s;
s = -1;
if (port != 0)
snprintf(servname, sizeof(servname), "%d", port);
else {
strncpy(servname, "cvsup", sizeof(servname) - 1);
servname[sizeof(servname) - 1] = '\0';
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(config->host, servname, &hints, &res);
/*
* Try with the hardcoded port number for OSes that don't
* have cvsup defined in the /etc/services file.
*/
if (error == EAI_SERVICE) {
strncpy(servname, "5999", sizeof(servname) - 1);
servname[sizeof(servname) - 1] = '\0';
error = getaddrinfo(config->host, servname, &hints, &res);
}
if (error) {
lprintf(0, "Name lookup failure for \"%s\": %s\n", config->host,
gai_strerror(error));
return (STATUS_TRANSIENTFAILURE);
}
for (ai = res; ai != NULL; ai = ai->ai_next) {
s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (s != -1) {
error = 0;
if (config->laddr != NULL) {
opt = 1;
(void)setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
&opt, sizeof(opt));
error = bind(s, config->laddr,
config->laddrlen);
}
if (!error) {
error = connect(s, ai->ai_addr, ai->ai_addrlen);
if (error && errno == EINTR)
error = proto_waitconnect(s);
}
if (error)
close(s);
}
(void)getnameinfo(ai->ai_addr, ai->ai_addrlen, addrbuf,
sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
if (s == -1 || error) {
lprintf(0, "Cannot connect to %s: %s\n", addrbuf,
strerror(errno));
continue;
}
lprintf(1, "Connected to %s\n", addrbuf);
freeaddrinfo(res);
config->socket = s;
return (STATUS_SUCCESS);
}
freeaddrinfo(res);
return (STATUS_TRANSIENTFAILURE);
}
/* Greet the server. */
static int
proto_greet(struct config *config)
{
char *line, *cmd, *msg, *swver;
struct stream *s;
s = config->server;
line = stream_getln(s, NULL);
cmd = proto_get_ascii(&line);
if (cmd == NULL)
goto bad;
if (strcmp(cmd, "OK") == 0) {
(void)proto_get_ascii(&line); /* major number */
(void)proto_get_ascii(&line); /* minor number */
swver = proto_get_ascii(&line);
} else if (strcmp(cmd, "!") == 0) {
msg = proto_get_rest(&line);
if (msg == NULL)
goto bad;
lprintf(-1, "Rejected by server: %s\n", msg);
return (STATUS_TRANSIENTFAILURE);
} else
goto bad;
lprintf(2, "Server software version: %s\n",
swver != NULL ? swver : ".");
return (STATUS_SUCCESS);
bad:
lprintf(-1, "Invalid greeting from server\n");
return (STATUS_FAILURE);
}
/* Negotiate protocol version with the server. */
static int
proto_negproto(struct config *config)
{
struct stream *s;
char *cmd, *line, *msg;
int error, maj, min;
s = config->server;
proto_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER);
stream_flush(s);
line = stream_getln(s, NULL);
cmd = proto_get_ascii(&line);
if (cmd == NULL || line == NULL)
goto bad;
if (strcmp(cmd, "!") == 0) {
msg = proto_get_rest(&line);
lprintf(-1, "Protocol negotiation failed: %s\n", msg);
return (1);
} else if (strcmp(cmd, "PROTO") != 0)
goto bad;
error = proto_get_int(&line, &maj, 10);
if (!error)
error = proto_get_int(&line, &min, 10);
if (error)
goto bad;
if (maj != PROTO_MAJ || min != PROTO_MIN) {
lprintf(-1, "Server protocol version %d.%d not supported "
"by client\n", maj, min);
return (STATUS_FAILURE);
}
return (STATUS_SUCCESS);
bad:
lprintf(-1, "Invalid PROTO command from server\n");
return (STATUS_FAILURE);
}
static int
proto_login(struct config *config)
{
struct stream *s;
char hostbuf[MAXHOSTNAMELEN];
char *line, *login, *host, *cmd, *realm, *challenge, *msg;
int error;
s = config->server;
error = gethostname(hostbuf, sizeof(hostbuf));
hostbuf[sizeof(hostbuf) - 1] = '\0';
if (error)
host = NULL;
else
host = hostbuf;
login = getlogin();
proto_printf(s, "USER %s %s\n", login != NULL ? login : "?",
host != NULL ? host : "?");
stream_flush(s);
line = stream_getln(s, NULL);
cmd = proto_get_ascii(&line);
realm = proto_get_ascii(&line);
challenge = proto_get_ascii(&line);
if (challenge == NULL || line != NULL)
goto bad;
if (strcmp(realm, ".") != 0 || strcmp(challenge, ".") != 0) {
lprintf(-1, "Authentication required by the server and not "
"supported by client\n");
return (STATUS_FAILURE);
}
proto_printf(s, "AUTHMD5 . . .\n");
stream_flush(s);
line = stream_getln(s, NULL);
cmd = proto_get_ascii(&line);
if (cmd == NULL || line == NULL)
goto bad;
if (strcmp(cmd, "OK") == 0)
return (STATUS_SUCCESS);
if (strcmp(cmd, "!") == 0) {
msg = proto_get_rest(&line);
if (msg == NULL)
goto bad;
lprintf(-1, "Server error: %s\n", msg);
return (STATUS_FAILURE);
}
bad:
lprintf(-1, "Invalid server reply to AUTHMD5\n");
return (STATUS_FAILURE);
}
/*
* File attribute support negotiation.
*/
static int
proto_fileattr(struct config *config)
{
fattr_support_t support;
struct stream *s;
char *line, *cmd;
int error, i, n, attr;
s = config->server;
lprintf(2, "Negotiating file attribute support\n");
proto_printf(s, "ATTR %d\n", FT_NUMBER);
for (i = 0; i < FT_NUMBER; i++)
proto_printf(s, "%x\n", fattr_supported(i));
proto_printf(s, ".\n");
stream_flush(s);
line = stream_getln(s, NULL);
if (line == NULL)
goto bad;
cmd = proto_get_ascii(&line);
error = proto_get_int(&line, &n, 10);
if (error || line != NULL || strcmp(cmd, "ATTR") != 0 || n > FT_NUMBER)
goto bad;
for (i = 0; i < n; i++) {
line = stream_getln(s, NULL);
if (line == NULL)
goto bad;
error = proto_get_int(&line, &attr, 16);
if (error)
goto bad;
support[i] = fattr_supported(i) & attr;
}
for (i = n; i < FT_NUMBER; i++)
support[i] = 0;
line = stream_getln(s, NULL);
if (line == NULL || strcmp(line, ".") != 0)
goto bad;
memcpy(config->fasupport, support, sizeof(config->fasupport));
return (STATUS_SUCCESS);
bad:
lprintf(-1, "Protocol error negotiating attribute support\n");
return (STATUS_FAILURE);
}
/*
* Exchange collection information.
*/
static int
proto_xchgcoll(struct config *config)
{
struct coll *coll;
struct stream *s;
struct globtree *diraccept, *dirrefuse;
struct globtree *fileaccept, *filerefuse;
char *line, *cmd, *collname, *pat;
char *msg, *release, *ident, *rcskey, *prefix;
size_t i, len;
int error, flags, options;
s = config->server;
lprintf(2, "Exchanging collection information\n");
STAILQ_FOREACH(coll, &config->colls, co_next) {
if (coll->co_options & CO_SKIP)
continue;
proto_printf(s, "COLL %s %s %o %d\n", coll->co_name,
coll->co_release, coll->co_umask, coll->co_options);
for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
proto_printf(s, "ACC %s\n",
pattlist_get(coll->co_accepts, i));
}
for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
proto_printf(s, "REF %s\n",
pattlist_get(coll->co_refusals, i));
}
proto_printf(s, ".\n");
}
proto_printf(s, ".\n");
stream_flush(s);
STAILQ_FOREACH(coll, &config->colls, co_next) {
if (coll->co_options & CO_SKIP)
continue;
coll->co_norsync = globtree_false();
line = stream_getln(s, NULL);
if (line == NULL)
goto bad;
cmd = proto_get_ascii(&line);
collname = proto_get_ascii(&line);
release = proto_get_ascii(&line);
error = proto_get_int(&line, &options, 10);
if (error || line != NULL)
goto bad;
if (strcmp(cmd, "COLL") != 0 ||
strcmp(collname, coll->co_name) != 0 ||
strcmp(release, coll->co_release) != 0)
goto bad;
coll->co_options =
(coll->co_options | (options & CO_SERVMAYSET)) &
~(~options & CO_SERVMAYCLEAR);
while ((line = stream_getln(s, NULL)) != NULL) {
if (strcmp(line, ".") == 0)
break;
cmd = proto_get_ascii(&line);
if (cmd == NULL)
goto bad;
if (strcmp(cmd, "!") == 0) {
msg = proto_get_rest(&line);
if (msg == NULL)
goto bad;
lprintf(-1, "Server message: %s\n", msg);
} else if (strcmp(cmd, "PRFX") == 0) {
prefix = proto_get_ascii(&line);
if (prefix == NULL || line != NULL)
goto bad;
coll->co_cvsroot = xstrdup(prefix);
} else if (strcmp(cmd, "KEYALIAS") == 0) {
ident = proto_get_ascii(&line);
rcskey = proto_get_ascii(&line);
if (rcskey == NULL || line != NULL)
goto bad;
error = keyword_alias(coll->co_keyword, ident,
rcskey);
if (error)
goto bad;
} else if (strcmp(cmd, "KEYON") == 0) {
ident = proto_get_ascii(&line);
if (ident == NULL || line != NULL)
goto bad;
error = keyword_enable(coll->co_keyword, ident);
if (error)
goto bad;
} else if (strcmp(cmd, "KEYOFF") == 0) {
ident = proto_get_ascii(&line);
if (ident == NULL || line != NULL)
goto bad;
error = keyword_disable(coll->co_keyword,
ident);
if (error)
goto bad;
} else if (strcmp(cmd, "NORS") == 0) {
pat = proto_get_ascii(&line);
if (pat == NULL || line != NULL)
goto bad;
coll->co_norsync = globtree_or(coll->co_norsync,
globtree_match(pat, FNM_PATHNAME));
} else if (strcmp(cmd, "RNORS") == 0) {
pat = proto_get_ascii(&line);
if (pat == NULL || line != NULL)
goto bad;
coll->co_norsync = globtree_or(coll->co_norsync,
globtree_match(pat, FNM_PATHNAME |
FNM_LEADING_DIR));
} else
goto bad;
}
if (line == NULL)
goto bad;
keyword_prepare(coll->co_keyword);
diraccept = globtree_true();
fileaccept = globtree_true();
dirrefuse = globtree_false();
filerefuse = globtree_false();
if (pattlist_size(coll->co_accepts) > 0) {
globtree_free(diraccept);
globtree_free(fileaccept);
diraccept = globtree_false();
fileaccept = globtree_false();
flags = FNM_PATHNAME | FNM_LEADING_DIR |
FNM_PREFIX_DIRS;
for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
pat = pattlist_get(coll->co_accepts, i);
diraccept = globtree_or(diraccept,
globtree_match(pat, flags));
len = strlen(pat);
if (coll->co_options & CO_CHECKOUTMODE &&
(len == 0 || pat[len - 1] != '*')) {
/* We must modify the pattern so that it
refers to the RCS file, rather than
the checked-out file. */
xasprintf(&pat, "%s,v", pat);
fileaccept = globtree_or(fileaccept,
globtree_match(pat, flags));
free(pat);
} else {
fileaccept = globtree_or(fileaccept,
globtree_match(pat, flags));
}
}
}
for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
pat = pattlist_get(coll->co_refusals, i);
dirrefuse = globtree_or(dirrefuse,
globtree_match(pat, 0));
len = strlen(pat);
if (coll->co_options & CO_CHECKOUTMODE &&
(len == 0 || pat[len - 1] != '*')) {
/* We must modify the pattern so that it refers
to the RCS file, rather than the checked-out
file. */
xasprintf(&pat, "%s,v", pat);
filerefuse = globtree_or(filerefuse,
globtree_match(pat, 0));
free(pat);
} else {
filerefuse = globtree_or(filerefuse,
globtree_match(pat, 0));
}
}
coll->co_dirfilter = globtree_and(diraccept,
globtree_not(dirrefuse));
coll->co_filefilter = globtree_and(fileaccept,
globtree_not(filerefuse));
/* Set up a mask of file attributes that we don't want to sync
with the server. */
if (!(coll->co_options & CO_SETOWNER))
coll->co_attrignore |= FA_OWNER | FA_GROUP;
if (!(coll->co_options & CO_SETMODE))
coll->co_attrignore |= FA_MODE;
if (!(coll->co_options & CO_SETFLAGS))
coll->co_attrignore |= FA_FLAGS;
}
return (STATUS_SUCCESS);
bad:
lprintf(-1, "Protocol error during collection exchange\n");
return (STATUS_FAILURE);
}
static struct mux *
proto_mux(struct config *config)
{
struct mux *m;
struct stream *s, *wr;
struct chan *chan0, *chan1;
int id;
s = config->server;
lprintf(2, "Establishing multiplexed-mode data connection\n");
proto_printf(s, "MUX\n");
stream_flush(s);
m = mux_open(config->socket, &chan0);
if (m == NULL) {
lprintf(-1, "Cannot open the multiplexer\n");
return (NULL);
}
id = chan_listen(m);
if (id == -1) {
lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno));
mux_close(m);
return (NULL);
}
wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL);
proto_printf(wr, "CHAN %d\n", id);
stream_close(wr);
chan1 = chan_accept(m, id);
if (chan1 == NULL) {
lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno));
mux_close(m);
return (NULL);
}
config->chan0 = chan0;
config->chan1 = chan1;
return (m);
}
/*
* Initializes the connection to the CVSup server, that is handle
* the protocol negotiation, logging in, exchanging file attributes
* support and collections information, and finally run the update
* session.
*/
int
proto_run(struct config *config)
{
struct thread_args lister_args;
struct thread_args detailer_args;
struct thread_args updater_args;
struct thread_args *args;
struct killer killer;
struct threads *workers;
struct mux *m;
int i, status;
/*
* We pass NULL for the close() function because we'll reuse
* the socket after the stream is closed.
*/
config->server = stream_open_fd(config->socket, stream_read_fd,
stream_write_fd, NULL);
status = proto_greet(config);
if (status == STATUS_SUCCESS)
status = proto_negproto(config);
if (status == STATUS_SUCCESS)
status = proto_login(config);
if (status == STATUS_SUCCESS)
status = proto_fileattr(config);
if (status == STATUS_SUCCESS)
status = proto_xchgcoll(config);
if (status != STATUS_SUCCESS)
return (status);
/* Multi-threaded action starts here. */
m = proto_mux(config);
if (m == NULL)
return (STATUS_FAILURE);
stream_close(config->server);
config->server = NULL;
config->fixups = fixups_new();
killer_start(&killer, m);
/* Start the worker threads. */
workers = threads_new();
args = &lister_args;
args->config = config;
args->status = -1;
args->errmsg = NULL;
args->rd = NULL;
args->wr = stream_open(config->chan0,
NULL, (stream_writefn_t *)chan_write, NULL);
threads_create(workers, lister, args);
args = &detailer_args;
args->config = config;
args->status = -1;
args->errmsg = NULL;
args->rd = stream_open(config->chan0,
(stream_readfn_t *)chan_read, NULL, NULL);
args->wr = stream_open(config->chan1,
NULL, (stream_writefn_t *)chan_write, NULL);
threads_create(workers, detailer, args);
args = &updater_args;
args->config = config;
args->status = -1;
args->errmsg = NULL;
args->rd = stream_open(config->chan1,
(stream_readfn_t *)chan_read, NULL, NULL);
args->wr = NULL;
threads_create(workers, updater, args);
lprintf(2, "Running\n");
/* Wait for all the worker threads to finish. */
status = STATUS_SUCCESS;
for (i = 0; i < 3; i++) {
args = threads_wait(workers);
if (args->rd != NULL)
stream_close(args->rd);
if (args->wr != NULL)
stream_close(args->wr);
if (args->status != STATUS_SUCCESS) {
assert(args->errmsg != NULL);
if (status == STATUS_SUCCESS) {
status = args->status;
/* Shutdown the multiplexer to wake up all
the other threads. */
mux_shutdown(m, args->errmsg, status);
}
free(args->errmsg);
}
}
threads_free(workers);
if (status == STATUS_SUCCESS) {
lprintf(2, "Shutting down connection to server\n");
chan_close(config->chan0);
chan_close(config->chan1);
chan_wait(config->chan0);
chan_wait(config->chan1);
mux_shutdown(m, NULL, STATUS_SUCCESS);
}
killer_stop(&killer);
fixups_free(config->fixups);
status = mux_close(m);
if (status == STATUS_SUCCESS) {
lprintf(1, "Finished successfully\n");
} else if (status == STATUS_INTERRUPTED) {
lprintf(-1, "Interrupted\n");
if (killer.killedby != -1)
kill(getpid(), killer.killedby);
}
return (status);
}
/*
* Write a string into the stream, escaping characters as needed.
* Characters escaped:
*
* SPACE -> "\_"
* TAB -> "\t"
* NEWLINE -> "\n"
* CR -> "\r"
* \ -> "\\"
*/
static int
proto_escape(struct stream *wr, const char *s)
{
size_t len;
ssize_t n;
char c;
/* Handle characters that need escaping. */
do {
len = strcspn(s, " \t\r\n\\");
n = stream_write(wr, s, len);
if (n == -1)
return (-1);
c = s[len];
switch (c) {
case ' ':
n = stream_write(wr, "\\_", 2);
break;
case '\t':
n = stream_write(wr, "\\t", 2);
break;
case '\r':
n = stream_write(wr, "\\r", 2);
break;
case '\n':
n = stream_write(wr, "\\n", 2);
break;
case '\\':
n = stream_write(wr, "\\\\", 2);
break;
}
if (n == -1)
return (-1);
s += len + 1;
} while (c != '\0');
return (0);
}
/*
* A simple printf() implementation specifically tailored for csup.
* List of the supported formats:
*
* %c Print a char.
* %d or %i Print an int as decimal.
* %x Print an int as hexadecimal.
* %o Print an int as octal.
* %t Print a time_t as decimal.
* %s Print a char * escaping some characters as needed.
* %S Print a char * without escaping.
* %f Print an encoded struct fattr *.
* %F Print an encoded struct fattr *, specifying the supported
* attributes.
*/
int
proto_printf(struct stream *wr, const char *format, ...)
{
fattr_support_t *support;
long long longval;
struct fattr *fa;
const char *fmt;
va_list ap;
char *cp, *s, *attr;
ssize_t n;
size_t size;
off_t off;
int rv, val, ignore;
char c;
n = 0;
rv = 0;
fmt = format;
va_start(ap, format);
while ((cp = strchr(fmt, '%')) != NULL) {
if (cp > fmt) {
n = stream_write(wr, fmt, cp - fmt);
if (n == -1)
return (-1);
}
if (*++cp == '\0')
goto done;
switch (*cp) {
case 'c':
c = va_arg(ap, int);
rv = stream_printf(wr, "%c", c);
break;
case 'd':
case 'i':
val = va_arg(ap, int);
rv = stream_printf(wr, "%d", val);
break;
case 'x':
val = va_arg(ap, int);
rv = stream_printf(wr, "%x", val);
break;
case 'o':
val = va_arg(ap, int);
rv = stream_printf(wr, "%o", val);
break;
case 'O':
off = va_arg(ap, off_t);
rv = stream_printf(wr, "%llu", off);
break;
case 'S':
s = va_arg(ap, char *);
assert(s != NULL);
rv = stream_printf(wr, "%s", s);
break;
case 's':
s = va_arg(ap, char *);
assert(s != NULL);
rv = proto_escape(wr, s);
break;
case 't':
longval = (long long)va_arg(ap, time_t);
rv = stream_printf(wr, "%lld", longval);
break;
case 'f':
fa = va_arg(ap, struct fattr *);
attr = fattr_encode(fa, NULL, 0);
rv = proto_escape(wr, attr);
free(attr);
break;
case 'F':
fa = va_arg(ap, struct fattr *);
support = va_arg(ap, fattr_support_t *);
ignore = va_arg(ap, int);
attr = fattr_encode(fa, *support, ignore);
rv = proto_escape(wr, attr);
free(attr);
break;
case 'z':
size = va_arg(ap, size_t);
rv = stream_printf(wr, "%zu", size);
break;
case '%':
n = stream_write(wr, "%", 1);
if (n == -1)
return (-1);
break;
}
if (rv == -1)
return (-1);
fmt = cp + 1;
}
if (*fmt != '\0') {
rv = stream_printf(wr, "%s", fmt);
if (rv == -1)
return (-1);
}
done:
va_end(ap);
return (0);
}
/*
* Unescape the string, see proto_escape().
*/
static void
proto_unescape(char *s)
{
char *cp, *cp2;
cp = s;
while ((cp = strchr(cp, '\\')) != NULL) {
switch (cp[1]) {
case '_':
*cp = ' ';
break;
case 't':
*cp = '\t';
break;
case 'r':
*cp = '\r';
break;
case 'n':
*cp = '\n';
break;
case '\\':
*cp = '\\';
break;
default:
*cp = *(cp + 1);
}
cp2 = ++cp;
while (*cp2 != '\0') {
*cp2 = *(cp2 + 1);
cp2++;
}
}
}
/*
* Get an ascii token in the string.
*/
char *
proto_get_ascii(char **s)
{
char *ret;
ret = strsep(s, " ");
if (ret == NULL)
return (NULL);
/* Make sure we disallow 0-length fields. */
if (*ret == '\0') {
*s = NULL;
return (NULL);
}
proto_unescape(ret);
return (ret);
}
/*
* Get the rest of the string.
*/
char *
proto_get_rest(char **s)
{
char *ret;
if (s == NULL)
return (NULL);
ret = *s;
proto_unescape(ret);
*s = NULL;
return (ret);
}
/*
* Get an int token.
*/
int
proto_get_int(char **s, int *val, int base)
{
char *cp;
int error;
cp = proto_get_ascii(s);
if (cp == NULL)
return (-1);
error = asciitoint(cp, val, base);
return (error);
}
/*
* Get a size_t token.
*/
int
proto_get_sizet(char **s, size_t *val, int base)
{
unsigned long long tmp;
char *cp, *end;
cp = proto_get_ascii(s);
if (cp == NULL)
return (-1);
errno = 0;
tmp = strtoll(cp, &end, base);
if (errno || *end != '\0')
return (-1);
*val = (size_t)tmp;
return (0);
}
/*
* Get a time_t token.
*
* Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
* is more portable and 64bits should be enough for a timestamp.
*/
int
proto_get_time(char **s, time_t *val)
{
long long tmp;
char *cp, *end;
cp = proto_get_ascii(s);
if (cp == NULL)
return (-1);
errno = 0;
tmp = strtoll(cp, &end, 10);
if (errno || *end != '\0')
return (-1);
*val = (time_t)tmp;
return (0);
}
/* Start the killer thread. It is used to protect against some signals
during the multi-threaded run so that we can gracefully fail. */
static void
killer_start(struct killer *k, struct mux *m)
{
int error;
k->mux = m;
k->killedby = -1;
sigemptyset(&k->sigset);
sigaddset(&k->sigset, SIGINT);
sigaddset(&k->sigset, SIGHUP);
sigaddset(&k->sigset, SIGTERM);
sigaddset(&k->sigset, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &k->sigset, NULL);
error = pthread_create(&k->thread, NULL, killer_run, k);
if (error)
err(1, "pthread_create");
}
/* The main loop of the killer thread. */
static void *
killer_run(void *arg)
{
struct killer *k;
int error, sig, old;
k = arg;
again:
error = sigwait(&k->sigset, &sig);
assert(!error);
if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
if (k->killedby == -1) {
k->killedby = sig;
/* Ensure we don't get canceled during the shutdown. */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
mux_shutdown(k->mux, "Cleaning up ...",
STATUS_INTERRUPTED);
pthread_setcancelstate(old, NULL);
}
}
goto again;
}
/* Stop the killer thread. */
static void
killer_stop(struct killer *k)
{
void *val;
int error;
error = pthread_cancel(k->thread);
assert(!error);
pthread_join(k->thread, &val);
assert(val == PTHREAD_CANCELED);
pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL);
}