xref: /trueos/usr.bin/csup/proto.c (revision b972b67ed72b5687a023c92602aaef64163b2f59)
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$
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 <inttypes.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 "auth.h"
50 #include "config.h"
51 #include "detailer.h"
52 #include "fattr.h"
53 #include "fixups.h"
54 #include "globtree.h"
55 #include "keyword.h"
56 #include "lister.h"
57 #include "misc.h"
58 #include "mux.h"
59 #include "proto.h"
60 #include "queue.h"
61 #include "stream.h"
62 #include "threads.h"
63 #include "updater.h"
64 
65 struct killer {
66 	pthread_t thread;
67 	sigset_t sigset;
68 	struct mux *mux;
69 	int killedby;
70 };
71 
72 static void		 killer_start(struct killer *, struct mux *);
73 static void		*killer_run(void *);
74 static void		 killer_stop(struct killer *);
75 
76 static int		 proto_waitconnect(int);
77 static int		 proto_greet(struct config *);
78 static int		 proto_negproto(struct config *);
79 static int		 proto_fileattr(struct config *);
80 static int		 proto_xchgcoll(struct config *);
81 static struct mux	*proto_mux(struct config *);
82 
83 static int		 proto_escape(struct stream *, const char *);
84 static void		 proto_unescape(char *);
85 
86 static int
proto_waitconnect(int s)87 proto_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. */
117 int
proto_connect(struct config * config,int family,uint16_t port)118 proto_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. */
187 static int
proto_greet(struct config * config)188 proto_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);
213 bad:
214 	lprintf(-1, "Invalid greeting from server\n");
215 	return (STATUS_FAILURE);
216 }
217 
218 /* Negotiate protocol version with the server. */
219 static int
proto_negproto(struct config * config)220 proto_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 (cmd == NULL || 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);
250 bad:
251 	lprintf(-1, "Invalid PROTO command from server\n");
252 	return (STATUS_FAILURE);
253 }
254 
255 /*
256  * File attribute support negotiation.
257  */
258 static int
proto_fileattr(struct config * config)259 proto_fileattr(struct config *config)
260 {
261 	fattr_support_t support;
262 	struct stream *s;
263 	char *line, *cmd;
264 	int error, i, n, attr;
265 
266 	s = config->server;
267 	lprintf(2, "Negotiating file attribute support\n");
268 	proto_printf(s, "ATTR %d\n", FT_NUMBER);
269 	for (i = 0; i < FT_NUMBER; i++)
270 		proto_printf(s, "%x\n", fattr_supported(i));
271 	proto_printf(s, ".\n");
272 	stream_flush(s);
273 	line = stream_getln(s, NULL);
274 	if (line == NULL)
275 		goto bad;
276 	cmd = proto_get_ascii(&line);
277 	error = proto_get_int(&line, &n, 10);
278 	if (error || line != NULL || strcmp(cmd, "ATTR") != 0 || n > FT_NUMBER)
279 		goto bad;
280 	for (i = 0; i < n; i++) {
281 		line = stream_getln(s, NULL);
282 		if (line == NULL)
283 			goto bad;
284 		error = proto_get_int(&line, &attr, 16);
285 		if (error)
286 			goto bad;
287 		support[i] = fattr_supported(i) & attr;
288 	}
289 	for (i = n; i < FT_NUMBER; i++)
290 		support[i] = 0;
291 	line = stream_getln(s, NULL);
292 	if (line == NULL || strcmp(line, ".") != 0)
293 		goto bad;
294 	memcpy(config->fasupport, support, sizeof(config->fasupport));
295 	return (STATUS_SUCCESS);
296 bad:
297 	lprintf(-1, "Protocol error negotiating attribute support\n");
298 	return (STATUS_FAILURE);
299 }
300 
301 /*
302  * Exchange collection information.
303  */
304 static int
proto_xchgcoll(struct config * config)305 proto_xchgcoll(struct config *config)
306 {
307 	struct coll *coll;
308 	struct stream *s;
309 	struct globtree *diraccept, *dirrefuse;
310 	struct globtree *fileaccept, *filerefuse;
311 	char *line, *cmd, *collname, *pat;
312 	char *msg, *release, *ident, *rcskey, *prefix;
313 	size_t i, len;
314 	int error, flags, options;
315 
316 	s = config->server;
317 	lprintf(2, "Exchanging collection information\n");
318 	STAILQ_FOREACH(coll, &config->colls, co_next) {
319 		if (coll->co_options & CO_SKIP)
320 			continue;
321 		proto_printf(s, "COLL %s %s %o %d\n", coll->co_name,
322 		    coll->co_release, coll->co_umask, coll->co_options);
323 		for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
324 		    proto_printf(s, "ACC %s\n",
325 			pattlist_get(coll->co_accepts, i));
326 		}
327 		for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
328 		    proto_printf(s, "REF %s\n",
329 			pattlist_get(coll->co_refusals, i));
330 		}
331 		proto_printf(s, ".\n");
332 	}
333 	proto_printf(s, ".\n");
334 	stream_flush(s);
335 
336 	STAILQ_FOREACH(coll, &config->colls, co_next) {
337 		if (coll->co_options & CO_SKIP)
338 			continue;
339 		coll->co_norsync = globtree_false();
340 		line = stream_getln(s, NULL);
341 		if (line == NULL)
342 			goto bad;
343 		cmd = proto_get_ascii(&line);
344 		collname = proto_get_ascii(&line);
345 		release = proto_get_ascii(&line);
346 		error = proto_get_int(&line, &options, 10);
347 		if (error || line != NULL)
348 			goto bad;
349 		if (strcmp(cmd, "COLL") != 0 ||
350 		    strcmp(collname, coll->co_name) != 0 ||
351 		    strcmp(release, coll->co_release) != 0)
352 			goto bad;
353 		coll->co_options =
354 		    (coll->co_options | (options & CO_SERVMAYSET)) &
355 		    ~(~options & CO_SERVMAYCLEAR);
356 		while ((line = stream_getln(s, NULL)) != NULL) {
357 		 	if (strcmp(line, ".") == 0)
358 				break;
359 			cmd = proto_get_ascii(&line);
360 			if (cmd == NULL)
361 				goto bad;
362 			if (strcmp(cmd, "!") == 0) {
363 				msg = proto_get_rest(&line);
364 				if (msg == NULL)
365 					goto bad;
366 				lprintf(-1, "Server message: %s\n", msg);
367 			} else if (strcmp(cmd, "PRFX") == 0) {
368 				prefix = proto_get_ascii(&line);
369 				if (prefix == NULL || line != NULL)
370 					goto bad;
371 				coll->co_cvsroot = xstrdup(prefix);
372 			} else if (strcmp(cmd, "KEYALIAS") == 0) {
373 				ident = proto_get_ascii(&line);
374 				rcskey = proto_get_ascii(&line);
375 				if (rcskey == NULL || line != NULL)
376 					goto bad;
377 				error = keyword_alias(coll->co_keyword, ident,
378 				    rcskey);
379 				if (error)
380 					goto bad;
381 			} else if (strcmp(cmd, "KEYON") == 0) {
382 				ident = proto_get_ascii(&line);
383 				if (ident == NULL || line != NULL)
384 					goto bad;
385 				error = keyword_enable(coll->co_keyword, ident);
386 				if (error)
387 					goto bad;
388 			} else if (strcmp(cmd, "KEYOFF") == 0) {
389 				ident = proto_get_ascii(&line);
390 				if (ident == NULL || line != NULL)
391 					goto bad;
392 				error = keyword_disable(coll->co_keyword,
393 				    ident);
394 				if (error)
395 					goto bad;
396 			} else if (strcmp(cmd, "NORS") == 0) {
397 				pat = proto_get_ascii(&line);
398 				if (pat == NULL || line != NULL)
399 					goto bad;
400 				coll->co_norsync = globtree_or(coll->co_norsync,
401 				    globtree_match(pat, FNM_PATHNAME));
402 			} else if (strcmp(cmd, "RNORS") == 0) {
403 				pat = proto_get_ascii(&line);
404 				if (pat == NULL || line != NULL)
405 					goto bad;
406 				coll->co_norsync = globtree_or(coll->co_norsync,
407 				    globtree_match(pat, FNM_PATHNAME |
408 				    FNM_LEADING_DIR));
409 			} else
410 				goto bad;
411 		}
412 		if (line == NULL)
413 			goto bad;
414 		keyword_prepare(coll->co_keyword);
415 
416 		diraccept = globtree_true();
417 		fileaccept = globtree_true();
418 		dirrefuse = globtree_false();
419 		filerefuse = globtree_false();
420 
421 		if (pattlist_size(coll->co_accepts) > 0) {
422 			globtree_free(diraccept);
423 			globtree_free(fileaccept);
424 			diraccept = globtree_false();
425 			fileaccept = globtree_false();
426 			flags = FNM_PATHNAME | FNM_LEADING_DIR |
427 			    FNM_PREFIX_DIRS;
428 			for (i = 0; i < pattlist_size(coll->co_accepts); i++) {
429 				pat = pattlist_get(coll->co_accepts, i);
430 				diraccept = globtree_or(diraccept,
431 				    globtree_match(pat, flags));
432 
433 				len = strlen(pat);
434 				if (coll->co_options & CO_CHECKOUTMODE &&
435 				    (len == 0 || pat[len - 1] != '*')) {
436 					/* We must modify the pattern so that it
437 					   refers to the RCS file, rather than
438 					   the checked-out file. */
439 					xasprintf(&pat, "%s,v", pat);
440 					fileaccept = globtree_or(fileaccept,
441 					    globtree_match(pat, flags));
442 					free(pat);
443 				} else {
444 					fileaccept = globtree_or(fileaccept,
445 					    globtree_match(pat, flags));
446 				}
447 			}
448 		}
449 
450 		for (i = 0; i < pattlist_size(coll->co_refusals); i++) {
451 			pat = pattlist_get(coll->co_refusals, i);
452 			dirrefuse = globtree_or(dirrefuse,
453 			    globtree_match(pat, 0));
454 			len = strlen(pat);
455 			if (coll->co_options & CO_CHECKOUTMODE &&
456 			    (len == 0 || pat[len - 1] != '*')) {
457 				/* We must modify the pattern so that it refers
458 				   to the RCS file, rather than the checked-out
459 				   file. */
460 				xasprintf(&pat, "%s,v", pat);
461 				filerefuse = globtree_or(filerefuse,
462 				    globtree_match(pat, 0));
463 				free(pat);
464 			} else {
465 				filerefuse = globtree_or(filerefuse,
466 				    globtree_match(pat, 0));
467 			}
468 		}
469 
470 		coll->co_dirfilter = globtree_and(diraccept,
471 		    globtree_not(dirrefuse));
472 		coll->co_filefilter = globtree_and(fileaccept,
473 		    globtree_not(filerefuse));
474 
475 		/* Set up a mask of file attributes that we don't want to sync
476 		   with the server. */
477 		if (!(coll->co_options & CO_SETOWNER))
478 			coll->co_attrignore |= FA_OWNER | FA_GROUP;
479 		if (!(coll->co_options & CO_SETMODE))
480 			coll->co_attrignore |= FA_MODE;
481 		if (!(coll->co_options & CO_SETFLAGS))
482 			coll->co_attrignore |= FA_FLAGS;
483 	}
484 	return (STATUS_SUCCESS);
485 bad:
486 	lprintf(-1, "Protocol error during collection exchange\n");
487 	return (STATUS_FAILURE);
488 }
489 
490 static struct mux *
proto_mux(struct config * config)491 proto_mux(struct config *config)
492 {
493 	struct mux *m;
494 	struct stream *s, *wr;
495 	struct chan *chan0, *chan1;
496 	int id;
497 
498 	s = config->server;
499 	lprintf(2, "Establishing multiplexed-mode data connection\n");
500 	proto_printf(s, "MUX\n");
501 	stream_flush(s);
502 	m = mux_open(config->socket, &chan0);
503 	if (m == NULL) {
504 		lprintf(-1, "Cannot open the multiplexer\n");
505 		return (NULL);
506 	}
507 	id = chan_listen(m);
508 	if (id == -1) {
509 		lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno));
510 		mux_close(m);
511 		return (NULL);
512 	}
513 	wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL);
514 	proto_printf(wr, "CHAN %d\n", id);
515 	stream_close(wr);
516 	chan1 = chan_accept(m, id);
517 	if (chan1 == NULL) {
518 		lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno));
519 		mux_close(m);
520 		return (NULL);
521 	}
522 	config->chan0 = chan0;
523 	config->chan1 = chan1;
524 	return (m);
525 }
526 
527 /*
528  * Initializes the connection to the CVSup server, that is handle
529  * the protocol negotiation, logging in, exchanging file attributes
530  * support and collections information, and finally run the update
531  * session.
532  */
533 int
proto_run(struct config * config)534 proto_run(struct config *config)
535 {
536 	struct thread_args lister_args;
537 	struct thread_args detailer_args;
538 	struct thread_args updater_args;
539 	struct thread_args *args;
540 	struct killer killer;
541 	struct threads *workers;
542 	struct mux *m;
543 	int i, status;
544 
545 	/*
546 	 * We pass NULL for the close() function because we'll reuse
547 	 * the socket after the stream is closed.
548 	 */
549 	config->server = stream_open_fd(config->socket, stream_read_fd,
550 	    stream_write_fd, NULL);
551 	status = proto_greet(config);
552 	if (status == STATUS_SUCCESS)
553 		status = proto_negproto(config);
554 	if (status == STATUS_SUCCESS)
555 		status = auth_login(config);
556 	if (status == STATUS_SUCCESS)
557 		status = proto_fileattr(config);
558 	if (status == STATUS_SUCCESS)
559 		status = proto_xchgcoll(config);
560 	if (status != STATUS_SUCCESS)
561 		return (status);
562 
563 	/* Multi-threaded action starts here. */
564 	m = proto_mux(config);
565 	if (m == NULL)
566 		return (STATUS_FAILURE);
567 
568 	stream_close(config->server);
569 	config->server = NULL;
570 	config->fixups = fixups_new();
571 	killer_start(&killer, m);
572 
573 	/* Start the worker threads. */
574 	workers = threads_new();
575 	args = &lister_args;
576 	args->config = config;
577 	args->status = -1;
578 	args->errmsg = NULL;
579 	args->rd = NULL;
580 	args->wr = stream_open(config->chan0,
581 	    NULL, (stream_writefn_t *)chan_write, NULL);
582 	threads_create(workers, lister, args);
583 
584 	args = &detailer_args;
585 	args->config = config;
586 	args->status = -1;
587 	args->errmsg = NULL;
588 	args->rd = stream_open(config->chan0,
589 	    (stream_readfn_t *)chan_read, NULL, NULL);
590 	args->wr = stream_open(config->chan1,
591 	    NULL, (stream_writefn_t *)chan_write, NULL);
592 	threads_create(workers, detailer, args);
593 
594 	args = &updater_args;
595 	args->config = config;
596 	args->status = -1;
597 	args->errmsg = NULL;
598 	args->rd = stream_open(config->chan1,
599 	    (stream_readfn_t *)chan_read, NULL, NULL);
600 	args->wr = NULL;
601 	threads_create(workers, updater, args);
602 
603 	lprintf(2, "Running\n");
604 	/* Wait for all the worker threads to finish. */
605 	status = STATUS_SUCCESS;
606 	for (i = 0; i < 3; i++) {
607 		args = threads_wait(workers);
608 		if (args->rd != NULL)
609 			stream_close(args->rd);
610 		if (args->wr != NULL)
611 			stream_close(args->wr);
612 		if (args->status != STATUS_SUCCESS) {
613 			assert(args->errmsg != NULL);
614 			if (status == STATUS_SUCCESS) {
615 				status = args->status;
616 				/* Shutdown the multiplexer to wake up all
617 				   the other threads. */
618 				mux_shutdown(m, args->errmsg, status);
619 			}
620 			free(args->errmsg);
621 		}
622 	}
623 	threads_free(workers);
624 	if (status == STATUS_SUCCESS) {
625 		lprintf(2, "Shutting down connection to server\n");
626 		chan_close(config->chan0);
627 		chan_close(config->chan1);
628 		chan_wait(config->chan0);
629 		chan_wait(config->chan1);
630 		mux_shutdown(m, NULL, STATUS_SUCCESS);
631 	}
632 	killer_stop(&killer);
633 	fixups_free(config->fixups);
634 	status = mux_close(m);
635 	if (status == STATUS_SUCCESS) {
636 		lprintf(1, "Finished successfully\n");
637 	} else if (status == STATUS_INTERRUPTED) {
638 		lprintf(-1, "Interrupted\n");
639 		if (killer.killedby != -1)
640 			kill(getpid(), killer.killedby);
641 	}
642 	return (status);
643 }
644 
645 /*
646  * Write a string into the stream, escaping characters as needed.
647  * Characters escaped:
648  *
649  * SPACE	-> "\_"
650  * TAB		->  "\t"
651  * NEWLINE	-> "\n"
652  * CR		-> "\r"
653  * \		-> "\\"
654  */
655 static int
proto_escape(struct stream * wr,const char * s)656 proto_escape(struct stream *wr, const char *s)
657 {
658 	size_t len;
659 	ssize_t n;
660 	char c;
661 
662 	/* Handle characters that need escaping. */
663 	do {
664 		len = strcspn(s, " \t\r\n\\");
665 		n = stream_write(wr, s, len);
666 		if (n == -1)
667 			return (-1);
668 		c = s[len];
669 		switch (c) {
670 		case ' ':
671 			n = stream_write(wr, "\\_", 2);
672 			break;
673 		case '\t':
674 			n = stream_write(wr, "\\t", 2);
675 			break;
676 		case '\r':
677 			n = stream_write(wr, "\\r", 2);
678 			break;
679 		case '\n':
680 			n = stream_write(wr, "\\n", 2);
681 			break;
682 		case '\\':
683 			n = stream_write(wr, "\\\\", 2);
684 			break;
685 		}
686 		if (n == -1)
687 			return (-1);
688 		s += len + 1;
689 	} while (c != '\0');
690 	return (0);
691 }
692 
693 /*
694  * A simple printf() implementation specifically tailored for csup.
695  * List of the supported formats:
696  *
697  * %c		Print a char.
698  * %d or %i	Print an int as decimal.
699  * %x		Print an int as hexadecimal.
700  * %o		Print an int as octal.
701  * %t		Print a time_t as decimal.
702  * %s		Print a char * escaping some characters as needed.
703  * %S		Print a char * without escaping.
704  * %f		Print an encoded struct fattr *.
705  * %F		Print an encoded struct fattr *, specifying the supported
706  * 		attributes.
707  */
708 int
proto_printf(struct stream * wr,const char * format,...)709 proto_printf(struct stream *wr, const char *format, ...)
710 {
711 	fattr_support_t *support;
712 	long long longval;
713 	struct fattr *fa;
714 	const char *fmt;
715 	va_list ap;
716 	char *cp, *s, *attr;
717 	ssize_t n;
718 	size_t size;
719 	off_t off;
720 	int rv, val, ignore;
721 	char c;
722 
723 	n = 0;
724 	rv = 0;
725 	fmt = format;
726 	va_start(ap, format);
727 	while ((cp = strchr(fmt, '%')) != NULL) {
728 		if (cp > fmt) {
729 			n = stream_write(wr, fmt, cp - fmt);
730 			if (n == -1) {
731 				va_end(ap);
732 				return (-1);
733 			}
734 		}
735 		if (*++cp == '\0')
736 			goto done;
737 		switch (*cp) {
738 		case 'c':
739 			c = va_arg(ap, int);
740 			rv = stream_printf(wr, "%c", c);
741 			break;
742 		case 'd':
743 		case 'i':
744 			val = va_arg(ap, int);
745 			rv = stream_printf(wr, "%d", val);
746 			break;
747 		case 'x':
748 			val = va_arg(ap, int);
749 			rv = stream_printf(wr, "%x", val);
750 			break;
751 		case 'o':
752 			val = va_arg(ap, int);
753 			rv = stream_printf(wr, "%o", val);
754 			break;
755 		case 'O':
756 			off = va_arg(ap, off_t);
757 			rv = stream_printf(wr, "%" PRId64, off);
758 			break;
759 		case 'S':
760 			s = va_arg(ap, char *);
761 			assert(s != NULL);
762 			rv = stream_printf(wr, "%s", s);
763 			break;
764 		case 's':
765 			s = va_arg(ap, char *);
766 			assert(s != NULL);
767 			rv = proto_escape(wr, s);
768 			break;
769 		case 't':
770 			longval = (long long)va_arg(ap, time_t);
771 			rv = stream_printf(wr, "%lld", longval);
772 			break;
773 		case 'f':
774 			fa = va_arg(ap, struct fattr *);
775 			attr = fattr_encode(fa, NULL, 0);
776 			rv = proto_escape(wr, attr);
777 			free(attr);
778 			break;
779 		case 'F':
780 			fa = va_arg(ap, struct fattr *);
781 			support = va_arg(ap, fattr_support_t *);
782 			ignore = va_arg(ap, int);
783 			attr = fattr_encode(fa, *support, ignore);
784 			rv = proto_escape(wr, attr);
785 			free(attr);
786 			break;
787 		case 'z':
788 			size = va_arg(ap, size_t);
789 			rv = stream_printf(wr, "%zu", size);
790 			break;
791 
792 		case '%':
793 			n = stream_write(wr, "%", 1);
794 			if (n == -1) {
795 				va_end(ap);
796 				return (-1);
797 			}
798 			break;
799 		}
800 		if (rv == -1) {
801 			va_end(ap);
802 			return (-1);
803 		}
804 		fmt = cp + 1;
805 	}
806 	if (*fmt != '\0') {
807 		rv = stream_printf(wr, "%s", fmt);
808 		if (rv == -1) {
809 			va_end(ap);
810 			return (-1);
811 		}
812 	}
813 done:
814 	va_end(ap);
815 	return (0);
816 }
817 
818 /*
819  * Unescape the string, see proto_escape().
820  */
821 static void
proto_unescape(char * s)822 proto_unescape(char *s)
823 {
824 	char *cp, *cp2;
825 
826 	cp = s;
827 	while ((cp = strchr(cp, '\\')) != NULL) {
828 		switch (cp[1]) {
829 		case '_':
830 			*cp = ' ';
831 			break;
832 		case 't':
833 			*cp = '\t';
834 			break;
835 		case 'r':
836 			*cp = '\r';
837 			break;
838 		case 'n':
839 			*cp = '\n';
840 			break;
841 		case '\\':
842 			*cp = '\\';
843 			break;
844 		default:
845 			*cp = *(cp + 1);
846 		}
847 		cp2 = ++cp;
848 		while (*cp2 != '\0') {
849 			*cp2 = *(cp2 + 1);
850 			cp2++;
851 		}
852 	}
853 }
854 
855 /*
856  * Get an ascii token in the string.
857  */
858 char *
proto_get_ascii(char ** s)859 proto_get_ascii(char **s)
860 {
861 	char *ret;
862 
863 	ret = strsep(s, " ");
864 	if (ret == NULL)
865 		return (NULL);
866 	/* Make sure we disallow 0-length fields. */
867 	if (*ret == '\0') {
868 		*s = NULL;
869 		return (NULL);
870 	}
871 	proto_unescape(ret);
872 	return (ret);
873 }
874 
875 /*
876  * Get the rest of the string.
877  */
878 char *
proto_get_rest(char ** s)879 proto_get_rest(char **s)
880 {
881 	char *ret;
882 
883 	if (s == NULL)
884 		return (NULL);
885 	ret = *s;
886 	proto_unescape(ret);
887 	*s = NULL;
888 	return (ret);
889 }
890 
891 /*
892  * Get an int token.
893  */
894 int
proto_get_int(char ** s,int * val,int base)895 proto_get_int(char **s, int *val, int base)
896 {
897 	char *cp;
898 	int error;
899 
900 	cp = proto_get_ascii(s);
901 	if (cp == NULL)
902 		return (-1);
903 	error = asciitoint(cp, val, base);
904 	return (error);
905 }
906 
907 /*
908  * Get a size_t token.
909  */
910 int
proto_get_sizet(char ** s,size_t * val,int base)911 proto_get_sizet(char **s, size_t *val, int base)
912 {
913 	unsigned long long tmp;
914 	char *cp, *end;
915 
916 	cp = proto_get_ascii(s);
917 	if (cp == NULL)
918 		return (-1);
919 	errno = 0;
920 	tmp = strtoll(cp, &end, base);
921 	if (errno || *end != '\0')
922 		return (-1);
923 	*val = (size_t)tmp;
924 	return (0);
925 }
926 
927 /*
928  * Get a time_t token.
929  *
930  * Ideally, we would use an intmax_t and strtoimax() here, but strtoll()
931  * is more portable and 64bits should be enough for a timestamp.
932  */
933 int
proto_get_time(char ** s,time_t * val)934 proto_get_time(char **s, time_t *val)
935 {
936 	long long tmp;
937 	char *cp, *end;
938 
939 	cp = proto_get_ascii(s);
940 	if (cp == NULL)
941 		return (-1);
942 	errno = 0;
943 	tmp = strtoll(cp, &end, 10);
944 	if (errno || *end != '\0')
945 		return (-1);
946 	*val = (time_t)tmp;
947 	return (0);
948 }
949 
950 /* Start the killer thread.  It is used to protect against some signals
951    during the multi-threaded run so that we can gracefully fail.  */
952 static void
killer_start(struct killer * k,struct mux * m)953 killer_start(struct killer *k, struct mux *m)
954 {
955 	int error;
956 
957 	k->mux = m;
958 	k->killedby = -1;
959 	sigemptyset(&k->sigset);
960 	sigaddset(&k->sigset, SIGINT);
961 	sigaddset(&k->sigset, SIGHUP);
962 	sigaddset(&k->sigset, SIGTERM);
963 	sigaddset(&k->sigset, SIGPIPE);
964 	pthread_sigmask(SIG_BLOCK, &k->sigset, NULL);
965 	error = pthread_create(&k->thread, NULL, killer_run, k);
966 	if (error)
967 		err(1, "pthread_create");
968 }
969 
970 /* The main loop of the killer thread. */
971 static void *
killer_run(void * arg)972 killer_run(void *arg)
973 {
974 	struct killer *k;
975 	int error, sig, old;
976 
977 	k = arg;
978 again:
979 	error = sigwait(&k->sigset, &sig);
980 	assert(!error);
981 	if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) {
982 		if (k->killedby == -1) {
983 			k->killedby = sig;
984 			/* Ensure we don't get canceled during the shutdown. */
985 			pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
986 			mux_shutdown(k->mux, "Cleaning up ...",
987 			    STATUS_INTERRUPTED);
988 			pthread_setcancelstate(old, NULL);
989 		}
990 	}
991 	goto again;
992 }
993 
994 /* Stop the killer thread. */
995 static void
killer_stop(struct killer * k)996 killer_stop(struct killer *k)
997 {
998 	void *val;
999 	int error;
1000 
1001 	error = pthread_cancel(k->thread);
1002 	assert(!error);
1003 	pthread_join(k->thread, &val);
1004 	assert(val == PTHREAD_CANCELED);
1005 	pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL);
1006 }
1007