proto.c revision 156230
1/*-
2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: vendor/csup/dist/contrib/csup/proto.c 156230 2006-03-03 04:11:29Z mux $
27 */
28
29#include <sys/param.h>
30#include <sys/select.h>
31#include <sys/socket.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34
35#include <assert.h>
36#include <err.h>
37#include <errno.h>
38#include <limits.h>
39#include <netdb.h>
40#include <pthread.h>
41#include <signal.h>
42#include <stdarg.h>
43#include <stddef.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48
49#include "config.h"
50#include "detailer.h"
51#include "fattr.h"
52#include "fixups.h"
53#include "globtree.h"
54#include "keyword.h"
55#include "lister.h"
56#include "misc.h"
57#include "mux.h"
58#include "proto.h"
59#include "queue.h"
60#include "stream.h"
61#include "threads.h"
62#include "updater.h"
63
64struct killer {
65	pthread_t thread;
66	sigset_t sigset;
67	struct mux *mux;
68	int killedby;
69};
70
71static void		 killer_start(struct killer *, struct mux *);
72static void		*killer_run(void *);
73static void		 killer_stop(struct killer *);
74
75static int		 proto_waitconnect(int);
76static int		 proto_greet(struct config *);
77static int		 proto_negproto(struct config *);
78static int		 proto_login(struct config *);
79static int		 proto_fileattr(struct config *);
80static int		 proto_xchgcoll(struct config *);
81static struct mux	*proto_mux(struct config *);
82
83static int		 proto_escape(struct stream *, const char *);
84static void		 proto_unescape(char *);
85
86static int
87proto_waitconnect(int s)
88{
89	fd_set readfd;
90	socklen_t len;
91	int error, rv, soerror;
92
93	FD_ZERO(&readfd);
94	FD_SET(s, &readfd);
95
96	do {
97		rv = select(s + 1, &readfd, NULL, NULL, NULL);
98	} while (rv == -1 && errno == EINTR);
99	if (rv == -1)
100		return (-1);
101	/* Check that the connection was really successful. */
102	len = sizeof(soerror);
103	error = getsockopt(s, SOL_SOCKET, SO_ERROR, &soerror, &len);
104	if (error) {
105		/* We have no choice but faking an error here. */
106		errno = ECONNREFUSED;
107		return (-1);
108	}
109	if (soerror) {
110		errno = soerror;
111		return (-1);
112	}
113	return (0);
114}
115
116/* Connect to the CVSup server. */
117int
118proto_connect(struct config *config, int family, uint16_t port)
119{
120	char addrbuf[NI_MAXHOST];
121	/* Enough to hold sizeof("cvsup") or any port number. */
122	char servname[8];
123	struct addrinfo *res, *ai, hints;
124	int error, opt, s;
125
126	s = -1;
127	if (port != 0)
128		snprintf(servname, sizeof(servname), "%d", port);
129	else {
130		strncpy(servname, "cvsup", sizeof(servname) - 1);
131		servname[sizeof(servname) - 1] = '\0';
132	}
133	memset(&hints, 0, sizeof(hints));
134	hints.ai_family = family;
135	hints.ai_socktype = SOCK_STREAM;
136	error = getaddrinfo(config->host, servname, &hints, &res);
137	/*
138	 * Try with the hardcoded port number for OSes that don't
139	 * have cvsup defined in the /etc/services file.
140	 */
141	if (error == EAI_SERVICE) {
142		strncpy(servname, "5999", sizeof(servname) - 1);
143		servname[sizeof(servname) - 1] = '\0';
144		error = getaddrinfo(config->host, servname, &hints, &res);
145	}
146	if (error) {
147		lprintf(0, "Name lookup failure for \"%s\": %s\n", config->host,
148		    gai_strerror(error));
149		return (STATUS_TRANSIENTFAILURE);
150	}
151	for (ai = res; ai != NULL; ai = ai->ai_next) {
152		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
153		if (s != -1) {
154			error = 0;
155			if (config->laddr != NULL) {
156				opt = 1;
157				(void)setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
158				    &opt, sizeof(opt));
159				error = bind(s, config->laddr,
160				    config->laddrlen);
161			}
162			if (!error) {
163				error = connect(s, ai->ai_addr, ai->ai_addrlen);
164				if (error && errno == EINTR)
165					error = proto_waitconnect(s);
166			}
167			if (error)
168				close(s);
169		}
170		(void)getnameinfo(ai->ai_addr, ai->ai_addrlen, addrbuf,
171		    sizeof(addrbuf), NULL, 0, NI_NUMERICHOST);
172		if (s == -1 || error) {
173			lprintf(0, "Cannot connect to %s: %s\n", addrbuf,
174			    strerror(errno));
175			continue;
176		}
177		lprintf(1, "Connected to %s\n", addrbuf);
178		freeaddrinfo(res);
179		config->socket = s;
180		return (STATUS_SUCCESS);
181	}
182	freeaddrinfo(res);
183	return (STATUS_TRANSIENTFAILURE);
184}
185
186/* Greet the server. */
187static int
188proto_greet(struct config *config)
189{
190	char *line, *cmd, *msg, *swver;
191	struct stream *s;
192
193	s = config->server;
194	line = stream_getln(s, NULL);
195	cmd = proto_get_ascii(&line);
196	if (cmd == NULL)
197		goto bad;
198	if (strcmp(cmd, "OK") == 0) {
199		(void)proto_get_ascii(&line);	/* major number */
200		(void)proto_get_ascii(&line);	/* minor number */
201		swver = proto_get_ascii(&line);
202	} else if (strcmp(cmd, "!") == 0) {
203		msg = proto_get_rest(&line);
204		if (msg == NULL)
205			goto bad;
206		lprintf(-1, "Rejected by server: %s\n", msg);
207		return (STATUS_TRANSIENTFAILURE);
208	} else
209		goto bad;
210	lprintf(2, "Server software version: %s\n",
211	    swver != NULL ? swver : ".");
212	return (STATUS_SUCCESS);
213bad:
214	lprintf(-1, "Invalid greeting from server\n");
215	return (STATUS_FAILURE);
216}
217
218/* Negotiate protocol version with the server. */
219static int
220proto_negproto(struct config *config)
221{
222	struct stream *s;
223	char *cmd, *line, *msg;
224	int error, maj, min;
225
226	s = config->server;
227	proto_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER);
228	stream_flush(s);
229	line = stream_getln(s, NULL);
230	cmd = proto_get_ascii(&line);
231	if (line == NULL)
232		goto bad;
233	if (strcmp(cmd, "!") == 0) {
234		msg = proto_get_rest(&line);
235		lprintf(-1, "Protocol negotiation failed: %s\n", msg);
236		return (1);
237	} else if (strcmp(cmd, "PROTO") != 0)
238		goto bad;
239	error = proto_get_int(&line, &maj, 10);
240	if (!error)
241		error = proto_get_int(&line, &min, 10);
242	if (error)
243		goto bad;
244	if (maj != PROTO_MAJ || min != PROTO_MIN) {
245		lprintf(-1, "Server protocol version %d.%d not supported "
246		    "by client\n", maj, min);
247		return (STATUS_FAILURE);
248	}
249	return (STATUS_SUCCESS);
250bad:
251	lprintf(-1, "Invalid PROTO command from server\n");
252	return (STATUS_FAILURE);
253}
254
255static int
256proto_login(struct config *config)
257{
258	struct stream *s;
259	char host[MAXHOSTNAMELEN];
260	char *line, *cmd, *realm, *challenge, *msg;
261
262	s = config->server;
263	gethostname(host, sizeof(host));
264	host[sizeof(host) - 1] = '\0';
265	proto_printf(s, "USER %s %s\n", getlogin(), host);
266	stream_flush(s);
267	line = stream_getln(s, NULL);
268	cmd = proto_get_ascii(&line);
269	realm = proto_get_ascii(&line);
270	challenge = proto_get_ascii(&line);
271	if (challenge == NULL || line != NULL)
272		goto bad;
273	if (strcmp(realm, ".") != 0 || strcmp(challenge, ".") != 0) {
274		lprintf(-1, "Authentication required by the server and not "
275		    "supported by client\n");
276		return (STATUS_FAILURE);
277	}
278	proto_printf(s, "AUTHMD5 . . .\n");
279	stream_flush(s);
280	line = stream_getln(s, NULL);
281	cmd = proto_get_ascii(&line);
282	if (strcmp(cmd, "OK") == 0)
283		return (STATUS_SUCCESS);
284	if (strcmp(cmd, "!") == 0) {
285		msg = proto_get_rest(&line);
286		if (msg == NULL)
287			goto bad;
288		lprintf(-1, "Server error: %s\n", msg);
289		return (STATUS_FAILURE);
290	}
291bad:
292	lprintf(-1, "Invalid server reply to AUTHMD5\n");
293	return (STATUS_FAILURE);
294}
295
296/*
297 * File attribute support negotiation.
298 */
299static int
300proto_fileattr(struct config *config)
301{
302	fattr_support_t support;
303	struct stream *s;
304	char *line, *cmd;
305	int error, i, n, attr;
306
307	s = config->server;
308	lprintf(2, "Negotiating file attribute support\n");
309	proto_printf(s, "ATTR %d\n", FT_NUMBER);
310	for (i = 0; i < FT_NUMBER; i++)
311		proto_printf(s, "%x\n", fattr_supported(i));
312	proto_printf(s, ".\n");
313	stream_flush(s);
314	line = stream_getln(s, NULL);
315	if (line == NULL)
316		goto bad;
317	cmd = proto_get_ascii(&line);
318	error = proto_get_int(&line, &n, 10);
319	if (error || line != NULL || strcmp(cmd, "ATTR") != 0 || n > FT_NUMBER)
320		goto bad;
321	for (i = 0; i < n; i++) {
322		line = stream_getln(s, NULL);
323		if (line == NULL)
324			goto bad;
325		error = proto_get_int(&line, &attr, 16);
326		if (error)
327			goto bad;
328		support[i] = fattr_supported(i) & attr;
329	}
330	for (i = n; i < FT_NUMBER; i++)
331		support[i] = 0;
332	line = stream_getln(s, NULL);
333	if (line == NULL || strcmp(line, ".") != 0)
334		goto bad;
335	memcpy(config->fasupport, support, sizeof(config->fasupport));
336	return (STATUS_SUCCESS);
337bad:
338	lprintf(-1, "Protocol error negotiating attribute support\n");
339	return (STATUS_FAILURE);
340}
341
342/*
343 * Exchange collection information.
344 */
345static int
346proto_xchgcoll(struct config *config)
347{
348	struct coll *coll;
349	struct stream *s;
350	struct globtree *diraccept, *dirrefuse;
351	struct globtree *fileaccept, *filerefuse;
352	char *line, *cmd, *collname, *pat;
353	char *msg, *release, *ident, *rcskey, *prefix;
354	size_t i, len;
355	int error, flags, options;
356
357	s = config->server;
358	lprintf(2, "Exchanging collection information\n");
359	STAILQ_FOREACH(coll, &config->colls, co_next) {
360		proto_printf(s, "COLL %s %s %o %d\n", coll->co_name,
361		    coll->co_release, coll->co_umask, coll->co_options);
362		for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
363		    proto_printf(s, "ACC %s\n",
364			pattlist_get(coll->co_accepts, i));
365		}
366		for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
367		    proto_printf(s, "REF %s\n",
368			pattlist_get(coll->co_refusals, i));
369		}
370		proto_printf(s, ".\n");
371	}
372	proto_printf(s, ".\n");
373	stream_flush(s);
374	STAILQ_FOREACH(coll, &config->colls, co_next) {
375		if (coll->co_options & CO_SKIP)
376			continue;
377		line = stream_getln(s, NULL);
378		if (line == NULL)
379			goto bad;
380		cmd = proto_get_ascii(&line);
381		collname = proto_get_ascii(&line);
382		release = proto_get_ascii(&line);
383		error = proto_get_int(&line, &options, 10);
384		if (error || line != NULL)
385			goto bad;
386		if (strcmp(cmd, "COLL") != 0 ||
387		    strcmp(collname, coll->co_name) != 0 ||
388		    strcmp(release, coll->co_release) != 0)
389			goto bad;
390		coll->co_options =
391		    (coll->co_options | (options & CO_SERVMAYSET)) &
392		    ~(~options & CO_SERVMAYCLEAR);
393		while ((line = stream_getln(s, NULL)) != NULL) {
394		 	if (strcmp(line, ".") == 0)
395				break;
396			cmd = proto_get_ascii(&line);
397			if (cmd == NULL)
398				goto bad;
399			if (strcmp(cmd, "!") == 0) {
400				msg = proto_get_rest(&line);
401				if (msg == NULL)
402					goto bad;
403				lprintf(-1, "Server message: %s\n", msg);
404			} else if (strcmp(cmd, "PRFX") == 0) {
405				prefix = proto_get_ascii(&line);
406				if (prefix == NULL || line != NULL)
407					goto bad;
408				coll->co_cvsroot = xstrdup(prefix);
409			} else if (strcmp(cmd, "KEYALIAS") == 0) {
410				ident = proto_get_ascii(&line);
411				rcskey = proto_get_ascii(&line);
412				if (rcskey == NULL || line != NULL)
413					goto bad;
414				error = keyword_alias(coll->co_keyword, ident,
415				    rcskey);
416				if (error)
417					goto bad;
418			} else if (strcmp(cmd, "KEYON") == 0) {
419				ident = proto_get_ascii(&line);
420				if (ident == NULL || line != NULL)
421					goto bad;
422				error = keyword_enable(coll->co_keyword, ident);
423				if (error)
424					goto bad;
425			} else if (strcmp(cmd, "KEYOFF") == 0) {
426				ident = proto_get_ascii(&line);
427				if (ident == NULL || line != NULL)
428					goto bad;
429				error = keyword_disable(coll->co_keyword,
430				    ident);
431				if (error)
432					goto bad;
433			}
434		}
435		if (line == NULL)
436			goto bad;
437		keyword_prepare(coll->co_keyword);
438
439		diraccept = globtree_true();
440		fileaccept = globtree_true();
441		dirrefuse = globtree_false();
442		filerefuse = globtree_false();
443
444		if (pattlist_size(coll->co_accepts) > 0) {
445			globtree_free(diraccept);
446			globtree_free(fileaccept);
447			diraccept = globtree_false();
448			fileaccept = globtree_false();
449			flags = FNM_PATHNAME | FNM_LEADING_DIR |
450			    FNM_PREFIX_DIRS;
451			for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
452				pat = pattlist_get(coll->co_accepts, i);
453				diraccept = globtree_or(diraccept,
454				    globtree_match(pat, flags));
455
456				len = strlen(pat);
457				if (coll->co_options & CO_CHECKOUTMODE &&
458				    (len == 0 || pat[len - 1] != '*')) {
459					/* We must modify the pattern so that it
460					   refers to the RCS file, rather than
461					   the checked-out file. */
462					xasprintf(&pat, "%s,v", pat);
463					fileaccept = globtree_or(fileaccept,
464					    globtree_match(pat, flags));
465					free(pat);
466				} else {
467					fileaccept = globtree_or(fileaccept,
468					    globtree_match(pat, flags));
469				}
470			}
471		}
472
473		for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
474			pat = pattlist_get(coll->co_refusals, i);
475			dirrefuse = globtree_or(dirrefuse,
476			    globtree_match(pat, 0));
477			len = strlen(pat);
478			if (coll->co_options & CO_CHECKOUTMODE &&
479			    (len == 0 || pat[len - 1] != '*')) {
480				/* We must modify the pattern so that it refers
481				   to the RCS file, rather than the checked-out
482				   file. */
483				xasprintf(&pat, "%s,v", pat);
484				filerefuse = globtree_or(filerefuse,
485				    globtree_match(pat, 0));
486				free(pat);
487			} else {
488				filerefuse = globtree_or(filerefuse,
489				    globtree_match(pat, 0));
490			}
491		}
492
493		coll->co_dirfilter = globtree_and(diraccept,
494		    globtree_not(dirrefuse));
495		coll->co_filefilter = globtree_and(fileaccept,
496		    globtree_not(filerefuse));
497
498		/* At this point we don't need the pattern lists anymore. */
499		pattlist_free(coll->co_accepts);
500		pattlist_free(coll->co_refusals);
501		coll->co_accepts = NULL;
502		coll->co_refusals = NULL;
503
504		/* Set up a mask of file attributes that we don't want to sync
505		   with the server. */
506		if (!(coll->co_options & CO_SETOWNER))
507			coll->co_attrignore |= FA_OWNER | FA_GROUP;
508		if (!(coll->co_options & CO_SETMODE))
509			coll->co_attrignore |= FA_MODE;
510		if (!(coll->co_options & CO_SETFLAGS))
511			coll->co_attrignore |= FA_FLAGS;
512	}
513	return (STATUS_SUCCESS);
514bad:
515	lprintf(-1, "Protocol error during collection exchange\n");
516	return (STATUS_FAILURE);
517}
518
519static struct mux *
520proto_mux(struct config *config)
521{
522	struct mux *m;
523	struct stream *s, *wr;
524	struct chan *chan0, *chan1;
525	int id;
526
527	s = config->server;
528	lprintf(2, "Establishing multiplexed-mode data connection\n");
529	proto_printf(s, "MUX\n");
530	stream_flush(s);
531	m = mux_open(config->socket, &chan0);
532	if (m == NULL) {
533		lprintf(-1, "Cannot open the multiplexer\n");
534		return (NULL);
535	}
536	id = chan_listen(m);
537	if (id == -1) {
538		lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno));
539		mux_close(m);
540		return (NULL);
541	}
542	wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL);
543	proto_printf(wr, "CHAN %d\n", id);
544	stream_close(wr);
545	chan1 = chan_accept(m, id);
546	if (chan1 == NULL) {
547		lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno));
548		mux_close(m);
549		return (NULL);
550	}
551	config->chan0 = chan0;
552	config->chan1 = chan1;
553	return (m);
554}
555
556/*
557 * Initializes the connection to the CVSup server, that is handle
558 * the protocol negotiation, logging in, exchanging file attributes
559 * support and collections information, and finally run the update
560 * session.
561 */
562int
563proto_run(struct config *config)
564{
565	struct thread_args lister_args;
566	struct thread_args detailer_args;
567	struct thread_args updater_args;
568	struct thread_args *args;
569	struct killer killer;
570	struct threads *workers;
571	struct mux *m;
572	int i, status;
573
574	/*
575	 * We pass NULL for the close() function because we'll reuse
576	 * the socket after the stream is closed.
577	 */
578	config->server = stream_open_fd(config->socket, stream_read_fd,
579	    stream_write_fd, NULL);
580	status = proto_greet(config);
581	if (status == STATUS_SUCCESS)
582		status = proto_negproto(config);
583	if (status == STATUS_SUCCESS)
584		status = proto_login(config);
585	if (status == STATUS_SUCCESS)
586		status = proto_fileattr(config);
587	if (status == STATUS_SUCCESS)
588		status = proto_xchgcoll(config);
589	if (status != STATUS_SUCCESS)
590		return (status);
591
592	/* Multi-threaded action starts here. */
593	m = proto_mux(config);
594	if (m == NULL)
595		return (STATUS_FAILURE);
596
597	stream_close(config->server);
598	config->server = NULL;
599	config->fixups = fixups_new();
600	killer_start(&killer, m);
601
602	/* Start the worker threads. */
603	workers = threads_new();
604	args = &lister_args;
605	args->config = config;
606	args->status = -1;
607	args->errmsg = NULL;
608	args->rd = NULL;
609	args->wr = stream_open(config->chan0,
610	    NULL, (stream_writefn_t *)chan_write, NULL);
611	threads_create(workers, lister, args);
612
613	args = &detailer_args;
614	args->config = config;
615	args->status = -1;
616	args->errmsg = NULL;
617	args->rd = stream_open(config->chan0,
618	    (stream_readfn_t *)chan_read, NULL, NULL);
619	args->wr = stream_open(config->chan1,
620	    NULL, (stream_writefn_t *)chan_write, NULL);
621	threads_create(workers, detailer, args);
622
623	args = &updater_args;
624	args->config = config;
625	args->status = -1;
626	args->errmsg = NULL;
627	args->rd = stream_open(config->chan1,
628	    (stream_readfn_t *)chan_read, NULL, NULL);
629	args->wr = NULL;
630	threads_create(workers, updater, args);
631
632	lprintf(2, "Running\n");
633	/* Wait for all the worker threads to finish. */
634	status = STATUS_SUCCESS;
635	for (i = 0; i < 3; i++) {
636		args = threads_wait(workers);
637		if (args->rd != NULL)
638			stream_close(args->rd);
639		if (args->wr != NULL)
640			stream_close(args->wr);
641		if (args->status != STATUS_SUCCESS) {
642			assert(args->errmsg != NULL);
643			if (status == STATUS_SUCCESS) {
644				status = args->status;
645				/* Shutdown the multiplexer to wake up all
646				   the other threads. */
647				mux_shutdown(m, args->errmsg, status);
648			}
649			free(args->errmsg);
650		}
651	}
652	threads_free(workers);
653	if (status == STATUS_SUCCESS) {
654		lprintf(2, "Shutting down connection to server\n");
655		chan_close(config->chan0);
656		chan_close(config->chan1);
657		chan_wait(config->chan0);
658		chan_wait(config->chan1);
659		mux_shutdown(m, NULL, STATUS_SUCCESS);
660	}
661	killer_stop(&killer);
662	fixups_free(config->fixups);
663	status = mux_close(m);
664	if (status == STATUS_SUCCESS) {
665		lprintf(1, "Finished successfully\n");
666	} else if (status == STATUS_INTERRUPTED) {
667		lprintf(-1, "Interrupted\n");
668		if (killer.killedby != -1)
669			kill(getpid(), killer.killedby);
670	}
671	return (status);
672}
673
674/*
675 * Write a string into the stream, escaping characters as needed.
676 * Characters escaped:
677 *
678 * SPACE	-> "\_"
679 * TAB		->  "\t"
680 * NEWLINE	-> "\n"
681 * CR		-> "\r"
682 * \		-> "\\"
683 */
684static int
685proto_escape(struct stream *wr, const char *s)
686{
687	size_t len;
688	ssize_t n;
689	char c;
690
691	/* Handle characters that need escaping. */
692	do {
693		len = strcspn(s, " \t\r\n\\");
694		n = stream_write(wr, s, len);
695		if (n == -1)
696			return (-1);
697		c = s[len];
698		switch (c) {
699		case ' ':
700			n = stream_write(wr, "\\_", 2);
701			break;
702		case '\t':
703			n = stream_write(wr, "\\t", 2);
704			break;
705		case '\r':
706			n = stream_write(wr, "\\r", 2);
707			break;
708		case '\n':
709			n = stream_write(wr, "\\n", 2);
710			break;
711		case '\\':
712			n = stream_write(wr, "\\\\", 2);
713			break;
714		}
715		if (n == -1)
716			return (-1);
717		s += len + 1;
718	} while (c != '\0');
719	return (0);
720}
721
722/*
723 * A simple printf() implementation specifically tailored for csup.
724 * List of the supported formats:
725 *
726 * %c		Print a char.
727 * %d or %i	Print an int as decimal.
728 * %x		Print an int as hexadecimal.
729 * %o		Print an int as octal.
730 * %t		Print a time_t as decimal.
731 * %s		Print a char * escaping some characters as needed.
732 * %S		Print a char * without escaping.
733 * %f		Print an encoded struct fattr *.
734 * %F		Print an encoded struct fattr *, specifying the supported
735 * 		attributes.
736 */
737int
738proto_printf(struct stream *wr, const char *format, ...)
739{
740	fattr_support_t *support;
741	long long longval;
742	struct fattr *fa;
743	const char *fmt;
744	va_list ap;
745	char *cp, *s, *attr;
746	ssize_t n;
747	int rv, val, ignore;
748	char c;
749
750	n = 0;
751	rv = 0;
752	fmt = format;
753	va_start(ap, format);
754	while ((cp = strchr(fmt, '%')) != NULL) {
755		if (cp > fmt) {
756			n = stream_write(wr, fmt, cp - fmt);
757			if (n == -1)
758				return (-1);
759		}
760		if (*++cp == '\0')
761			goto done;
762		switch (*cp) {
763		case 'c':
764			c = va_arg(ap, int);
765			rv = stream_printf(wr, "%c", c);
766			break;
767		case 'd':
768		case 'i':
769			val = va_arg(ap, int);
770			rv = stream_printf(wr, "%d", val);
771			break;
772		case 'x':
773			val = va_arg(ap, int);
774			rv = stream_printf(wr, "%x", val);
775			break;
776		case 'o':
777			val = va_arg(ap, int);
778			rv = stream_printf(wr, "%o", val);
779			break;
780		case 'S':
781			s = va_arg(ap, char *);
782			assert(s != NULL);
783			rv = stream_printf(wr, "%s", s);
784			break;
785		case 's':
786			s = va_arg(ap, char *);
787			assert(s != NULL);
788			rv = proto_escape(wr, s);
789			break;
790		case 't':
791			longval = (long long)va_arg(ap, time_t);
792			rv = stream_printf(wr, "%lld", longval);
793			break;
794		case 'f':
795			fa = va_arg(ap, struct fattr *);
796			attr = fattr_encode(fa, NULL, 0);
797			rv = proto_escape(wr, attr);
798			free(attr);
799			break;
800		case 'F':
801			fa = va_arg(ap, struct fattr *);
802			support = va_arg(ap, fattr_support_t *);
803			ignore = va_arg(ap, int);
804			attr = fattr_encode(fa, *support, ignore);
805			rv = proto_escape(wr, attr);
806			free(attr);
807			break;
808		case '%':
809			n = stream_write(wr, "%", 1);
810			if (n == -1)
811				return (-1);
812			break;
813		}
814		if (rv == -1)
815			return (-1);
816		fmt = cp + 1;
817	}
818	if (*fmt != '\0') {
819		rv = stream_printf(wr, "%s", fmt);
820		if (rv == -1)
821			return (-1);
822	}
823done:
824	va_end(ap);
825	return (0);
826}
827
828/*
829 * Unescape the string, see proto_escape().
830 */
831static void
832proto_unescape(char *s)
833{
834	char *cp, *cp2;
835
836	cp = s;
837	while ((cp = strchr(cp, '\\')) != NULL) {
838		switch (cp[1]) {
839		case '_':
840			*cp = ' ';
841			break;
842		case 't':
843			*cp = '\t';
844			break;
845		case 'r':
846			*cp = '\r';
847			break;
848		case 'n':
849			*cp = '\n';
850			break;
851		case '\\':
852			*cp = '\\';
853			break;
854		default:
855			*cp = *(cp + 1);
856		}
857		cp2 = ++cp;
858		while (*cp2 != '\0') {
859			*cp2 = *(cp2 + 1);
860			cp2++;
861		}
862	}
863}
864
865/*
866 * Get an ascii token in the string.
867 */
868char *
869proto_get_ascii(char **s)
870{
871	char *ret;
872
873	ret = strsep(s, " ");
874	if (ret == NULL)
875		return (NULL);
876	/* Make sure we disallow 0-length fields. */
877	if (*ret == '\0') {
878		*s = NULL;
879		return (NULL);
880	}
881	proto_unescape(ret);
882	return (ret);
883}
884
885/*
886 * Get the rest of the string.
887 */
888char *
889proto_get_rest(char **s)
890{
891	char *ret;
892
893	if (s == NULL)
894		return (NULL);
895	ret = *s;
896	proto_unescape(ret);
897	*s = NULL;
898	return (ret);
899}
900
901/*
902 * Get an int token.
903 */
904int
905proto_get_int(char **s, int *val, int base)
906{
907	char *cp, *end;
908	long longval;
909
910	cp = proto_get_ascii(s);
911	if (cp == NULL)
912		return (-1);
913	errno = 0;
914	longval = strtol(cp, &end, base);
915	if (errno || *end != '\0')
916		return (-1);
917	if (longval > INT_MAX || longval < INT_MIN) {
918		errno = ERANGE;
919		return (-1);
920	}
921	*val = longval;
922	return (0);
923}
924
925/*
926 * Get a time_t token.
927 *
928 * Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
929 * is more portable and 64bits should be enough for a timestamp.
930 */
931int
932proto_get_time(char **s, time_t *val)
933{
934	long long tmp;
935	char *cp, *end;
936
937	cp = proto_get_ascii(s);
938	if (cp == NULL)
939		return (-1);
940	errno = 0;
941	tmp = strtoll(cp, &end, 10);
942	if (errno || *end != '\0')
943		return (-1);
944	*val = (time_t)tmp;
945	return (0);
946}
947
948/* Start the killer thread.  It is used to protect against some signals
949   during the multi-threaded run so that we can gracefully fail.  */
950static void
951killer_start(struct killer *k, struct mux *m)
952{
953	int error;
954
955	k->mux = m;
956	k->killedby = -1;
957	sigemptyset(&k->sigset);
958	sigaddset(&k->sigset, SIGINT);
959	sigaddset(&k->sigset, SIGHUP);
960	sigaddset(&k->sigset, SIGTERM);
961	sigaddset(&k->sigset, SIGPIPE);
962	pthread_sigmask(SIG_BLOCK, &k->sigset, NULL);
963	error = pthread_create(&k->thread, NULL, killer_run, k);
964	if (error)
965		err(1, "pthread_create");
966}
967
968/* The main loop of the killer thread. */
969static void *
970killer_run(void *arg)
971{
972	struct killer *k;
973	int error, sig, old;
974
975	k = arg;
976again:
977	error = sigwait(&k->sigset, &sig);
978	assert(!error);
979	if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
980		if (k->killedby == -1) {
981			k->killedby = sig;
982			/* Ensure we don't get canceled during the shutdown. */
983			pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
984			mux_shutdown(k->mux, "Cleaning up ...",
985			    STATUS_INTERRUPTED);
986			pthread_setcancelstate(old, NULL);
987		}
988	}
989	goto again;
990}
991
992/* Stop the killer thread. */
993static void
994killer_stop(struct killer *k)
995{
996	void *val;
997	int error;
998
999	error = pthread_cancel(k->thread);
1000	assert(!error);
1001	pthread_join(k->thread, &val);
1002	assert(val == PTHREAD_CANCELED);
1003	pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL);
1004}
1005