1156230Smux/*- 2156230Smux * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> 3156230Smux * All rights reserved. 4156230Smux * 5156230Smux * Redistribution and use in source and binary forms, with or without 6156230Smux * modification, are permitted provided that the following conditions 7156230Smux * are met: 8156230Smux * 1. Redistributions of source code must retain the above copyright 9156230Smux * notice, this list of conditions and the following disclaimer. 10156230Smux * 2. Redistributions in binary form must reproduce the above copyright 11156230Smux * notice, this list of conditions and the following disclaimer in the 12156230Smux * documentation and/or other materials provided with the distribution. 13156230Smux * 14156230Smux * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15156230Smux * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16156230Smux * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17156230Smux * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18156230Smux * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19156230Smux * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20156230Smux * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21156230Smux * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22156230Smux * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23156230Smux * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24156230Smux * SUCH DAMAGE. 25156230Smux * 26156230Smux * $FreeBSD$ 27156230Smux */ 28156230Smux 29156230Smux#include <sys/param.h> 30156230Smux#include <sys/select.h> 31156230Smux#include <sys/socket.h> 32156230Smux#include <sys/types.h> 33156230Smux#include <sys/stat.h> 34156230Smux 35156230Smux#include <assert.h> 36156230Smux#include <err.h> 37156230Smux#include <errno.h> 38228626Sdim#include <inttypes.h> 39156230Smux#include <netdb.h> 40156230Smux#include <pthread.h> 41156230Smux#include <signal.h> 42156230Smux#include <stdarg.h> 43156230Smux#include <stddef.h> 44156230Smux#include <stdio.h> 45156230Smux#include <stdlib.h> 46156230Smux#include <string.h> 47156230Smux#include <unistd.h> 48156230Smux 49203368Slulf#include "auth.h" 50156230Smux#include "config.h" 51156230Smux#include "detailer.h" 52156230Smux#include "fattr.h" 53156230Smux#include "fixups.h" 54156230Smux#include "globtree.h" 55156230Smux#include "keyword.h" 56156230Smux#include "lister.h" 57156230Smux#include "misc.h" 58156230Smux#include "mux.h" 59156230Smux#include "proto.h" 60156230Smux#include "queue.h" 61156230Smux#include "stream.h" 62156230Smux#include "threads.h" 63156230Smux#include "updater.h" 64156230Smux 65156230Smuxstruct killer { 66156230Smux pthread_t thread; 67156230Smux sigset_t sigset; 68156230Smux struct mux *mux; 69156230Smux int killedby; 70156230Smux}; 71156230Smux 72156230Smuxstatic void killer_start(struct killer *, struct mux *); 73156230Smuxstatic void *killer_run(void *); 74156230Smuxstatic void killer_stop(struct killer *); 75156230Smux 76156230Smuxstatic int proto_waitconnect(int); 77156230Smuxstatic int proto_greet(struct config *); 78156230Smuxstatic int proto_negproto(struct config *); 79156230Smuxstatic int proto_fileattr(struct config *); 80156230Smuxstatic int proto_xchgcoll(struct config *); 81156230Smuxstatic struct mux *proto_mux(struct config *); 82156230Smux 83156230Smuxstatic int proto_escape(struct stream *, const char *); 84156230Smuxstatic void proto_unescape(char *); 85156230Smux 86156230Smuxstatic int 87156230Smuxproto_waitconnect(int s) 88156230Smux{ 89156230Smux fd_set readfd; 90156230Smux socklen_t len; 91156230Smux int error, rv, soerror; 92156230Smux 93156230Smux FD_ZERO(&readfd); 94156230Smux FD_SET(s, &readfd); 95156230Smux 96156230Smux do { 97156230Smux rv = select(s + 1, &readfd, NULL, NULL, NULL); 98156230Smux } while (rv == -1 && errno == EINTR); 99156230Smux if (rv == -1) 100156230Smux return (-1); 101156230Smux /* Check that the connection was really successful. */ 102156230Smux len = sizeof(soerror); 103156230Smux error = getsockopt(s, SOL_SOCKET, SO_ERROR, &soerror, &len); 104156230Smux if (error) { 105156230Smux /* We have no choice but faking an error here. */ 106156230Smux errno = ECONNREFUSED; 107156230Smux return (-1); 108156230Smux } 109156230Smux if (soerror) { 110156230Smux errno = soerror; 111156230Smux return (-1); 112156230Smux } 113156230Smux return (0); 114156230Smux} 115156230Smux 116156230Smux/* Connect to the CVSup server. */ 117156230Smuxint 118156230Smuxproto_connect(struct config *config, int family, uint16_t port) 119156230Smux{ 120156230Smux char addrbuf[NI_MAXHOST]; 121156230Smux /* Enough to hold sizeof("cvsup") or any port number. */ 122156230Smux char servname[8]; 123156230Smux struct addrinfo *res, *ai, hints; 124156230Smux int error, opt, s; 125156230Smux 126156230Smux s = -1; 127156230Smux if (port != 0) 128156230Smux snprintf(servname, sizeof(servname), "%d", port); 129156230Smux else { 130156230Smux strncpy(servname, "cvsup", sizeof(servname) - 1); 131156230Smux servname[sizeof(servname) - 1] = '\0'; 132156230Smux } 133156230Smux memset(&hints, 0, sizeof(hints)); 134156230Smux hints.ai_family = family; 135156230Smux hints.ai_socktype = SOCK_STREAM; 136156230Smux error = getaddrinfo(config->host, servname, &hints, &res); 137156230Smux /* 138156230Smux * Try with the hardcoded port number for OSes that don't 139156230Smux * have cvsup defined in the /etc/services file. 140156230Smux */ 141156230Smux if (error == EAI_SERVICE) { 142156230Smux strncpy(servname, "5999", sizeof(servname) - 1); 143156230Smux servname[sizeof(servname) - 1] = '\0'; 144156230Smux error = getaddrinfo(config->host, servname, &hints, &res); 145156230Smux } 146156230Smux if (error) { 147156230Smux lprintf(0, "Name lookup failure for \"%s\": %s\n", config->host, 148156230Smux gai_strerror(error)); 149156230Smux return (STATUS_TRANSIENTFAILURE); 150156230Smux } 151156230Smux for (ai = res; ai != NULL; ai = ai->ai_next) { 152156230Smux s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); 153156230Smux if (s != -1) { 154156230Smux error = 0; 155156230Smux if (config->laddr != NULL) { 156156230Smux opt = 1; 157156230Smux (void)setsockopt(s, SOL_SOCKET, SO_REUSEADDR, 158156230Smux &opt, sizeof(opt)); 159156230Smux error = bind(s, config->laddr, 160156230Smux config->laddrlen); 161156230Smux } 162156230Smux if (!error) { 163156230Smux error = connect(s, ai->ai_addr, ai->ai_addrlen); 164156230Smux if (error && errno == EINTR) 165156230Smux error = proto_waitconnect(s); 166156230Smux } 167156230Smux if (error) 168156230Smux close(s); 169156230Smux } 170156230Smux (void)getnameinfo(ai->ai_addr, ai->ai_addrlen, addrbuf, 171156230Smux sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); 172156230Smux if (s == -1 || error) { 173156230Smux lprintf(0, "Cannot connect to %s: %s\n", addrbuf, 174156230Smux strerror(errno)); 175156230Smux continue; 176156230Smux } 177156230Smux lprintf(1, "Connected to %s\n", addrbuf); 178156230Smux freeaddrinfo(res); 179156230Smux config->socket = s; 180156230Smux return (STATUS_SUCCESS); 181156230Smux } 182156230Smux freeaddrinfo(res); 183156230Smux return (STATUS_TRANSIENTFAILURE); 184156230Smux} 185156230Smux 186156230Smux/* Greet the server. */ 187156230Smuxstatic int 188156230Smuxproto_greet(struct config *config) 189156230Smux{ 190156230Smux char *line, *cmd, *msg, *swver; 191156230Smux struct stream *s; 192156230Smux 193156230Smux s = config->server; 194156230Smux line = stream_getln(s, NULL); 195156230Smux cmd = proto_get_ascii(&line); 196156230Smux if (cmd == NULL) 197156230Smux goto bad; 198156230Smux if (strcmp(cmd, "OK") == 0) { 199156230Smux (void)proto_get_ascii(&line); /* major number */ 200156230Smux (void)proto_get_ascii(&line); /* minor number */ 201156230Smux swver = proto_get_ascii(&line); 202156230Smux } else if (strcmp(cmd, "!") == 0) { 203156230Smux msg = proto_get_rest(&line); 204156230Smux if (msg == NULL) 205156230Smux goto bad; 206156230Smux lprintf(-1, "Rejected by server: %s\n", msg); 207156230Smux return (STATUS_TRANSIENTFAILURE); 208156230Smux } else 209156230Smux goto bad; 210156230Smux lprintf(2, "Server software version: %s\n", 211156230Smux swver != NULL ? swver : "."); 212156230Smux return (STATUS_SUCCESS); 213156230Smuxbad: 214156230Smux lprintf(-1, "Invalid greeting from server\n"); 215156230Smux return (STATUS_FAILURE); 216156230Smux} 217156230Smux 218156230Smux/* Negotiate protocol version with the server. */ 219156230Smuxstatic int 220156230Smuxproto_negproto(struct config *config) 221156230Smux{ 222156230Smux struct stream *s; 223156230Smux char *cmd, *line, *msg; 224156230Smux int error, maj, min; 225156230Smux 226156230Smux s = config->server; 227156230Smux proto_printf(s, "PROTO %d %d %s\n", PROTO_MAJ, PROTO_MIN, PROTO_SWVER); 228156230Smux stream_flush(s); 229156230Smux line = stream_getln(s, NULL); 230156230Smux cmd = proto_get_ascii(&line); 231156701Smux if (cmd == NULL || line == NULL) 232156230Smux goto bad; 233156230Smux if (strcmp(cmd, "!") == 0) { 234156230Smux msg = proto_get_rest(&line); 235156230Smux lprintf(-1, "Protocol negotiation failed: %s\n", msg); 236156230Smux return (1); 237156230Smux } else if (strcmp(cmd, "PROTO") != 0) 238156230Smux goto bad; 239156230Smux error = proto_get_int(&line, &maj, 10); 240156230Smux if (!error) 241156230Smux error = proto_get_int(&line, &min, 10); 242156230Smux if (error) 243156230Smux goto bad; 244156230Smux if (maj != PROTO_MAJ || min != PROTO_MIN) { 245156230Smux lprintf(-1, "Server protocol version %d.%d not supported " 246156230Smux "by client\n", maj, min); 247156230Smux return (STATUS_FAILURE); 248156230Smux } 249156230Smux return (STATUS_SUCCESS); 250156230Smuxbad: 251156230Smux lprintf(-1, "Invalid PROTO command from server\n"); 252156230Smux return (STATUS_FAILURE); 253156230Smux} 254156230Smux 255156230Smux/* 256156230Smux * File attribute support negotiation. 257156230Smux */ 258156230Smuxstatic int 259156230Smuxproto_fileattr(struct config *config) 260156230Smux{ 261156230Smux fattr_support_t support; 262156230Smux struct stream *s; 263156230Smux char *line, *cmd; 264156230Smux int error, i, n, attr; 265156230Smux 266156230Smux s = config->server; 267156230Smux lprintf(2, "Negotiating file attribute support\n"); 268156230Smux proto_printf(s, "ATTR %d\n", FT_NUMBER); 269156230Smux for (i = 0; i < FT_NUMBER; i++) 270156230Smux proto_printf(s, "%x\n", fattr_supported(i)); 271156230Smux proto_printf(s, ".\n"); 272156230Smux stream_flush(s); 273156230Smux line = stream_getln(s, NULL); 274156230Smux if (line == NULL) 275156230Smux goto bad; 276156230Smux cmd = proto_get_ascii(&line); 277156230Smux error = proto_get_int(&line, &n, 10); 278156230Smux if (error || line != NULL || strcmp(cmd, "ATTR") != 0 || n > FT_NUMBER) 279156230Smux goto bad; 280156230Smux for (i = 0; i < n; i++) { 281156230Smux line = stream_getln(s, NULL); 282156230Smux if (line == NULL) 283156230Smux goto bad; 284156230Smux error = proto_get_int(&line, &attr, 16); 285156230Smux if (error) 286156230Smux goto bad; 287156230Smux support[i] = fattr_supported(i) & attr; 288156230Smux } 289156230Smux for (i = n; i < FT_NUMBER; i++) 290156230Smux support[i] = 0; 291156230Smux line = stream_getln(s, NULL); 292156230Smux if (line == NULL || strcmp(line, ".") != 0) 293156230Smux goto bad; 294156230Smux memcpy(config->fasupport, support, sizeof(config->fasupport)); 295156230Smux return (STATUS_SUCCESS); 296156230Smuxbad: 297156230Smux lprintf(-1, "Protocol error negotiating attribute support\n"); 298156230Smux return (STATUS_FAILURE); 299156230Smux} 300156230Smux 301156230Smux/* 302156230Smux * Exchange collection information. 303156230Smux */ 304156230Smuxstatic int 305156230Smuxproto_xchgcoll(struct config *config) 306156230Smux{ 307156230Smux struct coll *coll; 308156230Smux struct stream *s; 309156230Smux struct globtree *diraccept, *dirrefuse; 310156230Smux struct globtree *fileaccept, *filerefuse; 311156230Smux char *line, *cmd, *collname, *pat; 312156230Smux char *msg, *release, *ident, *rcskey, *prefix; 313156230Smux size_t i, len; 314156230Smux int error, flags, options; 315156230Smux 316156230Smux s = config->server; 317156230Smux lprintf(2, "Exchanging collection information\n"); 318156230Smux STAILQ_FOREACH(coll, &config->colls, co_next) { 319186781Slulf if (coll->co_options & CO_SKIP) 320186781Slulf continue; 321156230Smux proto_printf(s, "COLL %s %s %o %d\n", coll->co_name, 322156230Smux coll->co_release, coll->co_umask, coll->co_options); 323156230Smux for (i = 0; i < pattlist_size(coll->co_accepts); i++) { 324156230Smux proto_printf(s, "ACC %s\n", 325156230Smux pattlist_get(coll->co_accepts, i)); 326156230Smux } 327156230Smux for (i = 0; i < pattlist_size(coll->co_refusals); i++) { 328156230Smux proto_printf(s, "REF %s\n", 329156230Smux pattlist_get(coll->co_refusals, i)); 330156230Smux } 331156230Smux proto_printf(s, ".\n"); 332156230Smux } 333156230Smux proto_printf(s, ".\n"); 334156230Smux stream_flush(s); 335156701Smux 336156230Smux STAILQ_FOREACH(coll, &config->colls, co_next) { 337156230Smux if (coll->co_options & CO_SKIP) 338156230Smux continue; 339156701Smux coll->co_norsync = globtree_false(); 340156230Smux line = stream_getln(s, NULL); 341156230Smux if (line == NULL) 342156230Smux goto bad; 343156230Smux cmd = proto_get_ascii(&line); 344156230Smux collname = proto_get_ascii(&line); 345156230Smux release = proto_get_ascii(&line); 346156230Smux error = proto_get_int(&line, &options, 10); 347156230Smux if (error || line != NULL) 348156230Smux goto bad; 349156230Smux if (strcmp(cmd, "COLL") != 0 || 350156230Smux strcmp(collname, coll->co_name) != 0 || 351156230Smux strcmp(release, coll->co_release) != 0) 352156230Smux goto bad; 353156230Smux coll->co_options = 354156230Smux (coll->co_options | (options & CO_SERVMAYSET)) & 355156230Smux ~(~options & CO_SERVMAYCLEAR); 356156230Smux while ((line = stream_getln(s, NULL)) != NULL) { 357156230Smux if (strcmp(line, ".") == 0) 358156230Smux break; 359156230Smux cmd = proto_get_ascii(&line); 360156230Smux if (cmd == NULL) 361156230Smux goto bad; 362156230Smux if (strcmp(cmd, "!") == 0) { 363156230Smux msg = proto_get_rest(&line); 364156230Smux if (msg == NULL) 365156230Smux goto bad; 366156230Smux lprintf(-1, "Server message: %s\n", msg); 367156230Smux } else if (strcmp(cmd, "PRFX") == 0) { 368156230Smux prefix = proto_get_ascii(&line); 369156230Smux if (prefix == NULL || line != NULL) 370156230Smux goto bad; 371156230Smux coll->co_cvsroot = xstrdup(prefix); 372156230Smux } else if (strcmp(cmd, "KEYALIAS") == 0) { 373156230Smux ident = proto_get_ascii(&line); 374156230Smux rcskey = proto_get_ascii(&line); 375156230Smux if (rcskey == NULL || line != NULL) 376156230Smux goto bad; 377156230Smux error = keyword_alias(coll->co_keyword, ident, 378156230Smux rcskey); 379156230Smux if (error) 380156230Smux goto bad; 381156230Smux } else if (strcmp(cmd, "KEYON") == 0) { 382156230Smux ident = proto_get_ascii(&line); 383156230Smux if (ident == NULL || line != NULL) 384156230Smux goto bad; 385156230Smux error = keyword_enable(coll->co_keyword, ident); 386156230Smux if (error) 387156230Smux goto bad; 388156230Smux } else if (strcmp(cmd, "KEYOFF") == 0) { 389156230Smux ident = proto_get_ascii(&line); 390156230Smux if (ident == NULL || line != NULL) 391156230Smux goto bad; 392156230Smux error = keyword_disable(coll->co_keyword, 393156230Smux ident); 394156230Smux if (error) 395156230Smux goto bad; 396156701Smux } else if (strcmp(cmd, "NORS") == 0) { 397156701Smux pat = proto_get_ascii(&line); 398156701Smux if (pat == NULL || line != NULL) 399156701Smux goto bad; 400156701Smux coll->co_norsync = globtree_or(coll->co_norsync, 401156701Smux globtree_match(pat, FNM_PATHNAME)); 402156701Smux } else if (strcmp(cmd, "RNORS") == 0) { 403156701Smux pat = proto_get_ascii(&line); 404156701Smux if (pat == NULL || line != NULL) 405156701Smux goto bad; 406156701Smux coll->co_norsync = globtree_or(coll->co_norsync, 407156701Smux globtree_match(pat, FNM_PATHNAME | 408156701Smux FNM_LEADING_DIR)); 409156701Smux } else 410156701Smux goto bad; 411156230Smux } 412156230Smux if (line == NULL) 413156230Smux goto bad; 414156230Smux keyword_prepare(coll->co_keyword); 415156230Smux 416156230Smux diraccept = globtree_true(); 417156230Smux fileaccept = globtree_true(); 418156230Smux dirrefuse = globtree_false(); 419156230Smux filerefuse = globtree_false(); 420156230Smux 421156230Smux if (pattlist_size(coll->co_accepts) > 0) { 422156230Smux globtree_free(diraccept); 423156230Smux globtree_free(fileaccept); 424156230Smux diraccept = globtree_false(); 425156230Smux fileaccept = globtree_false(); 426156230Smux flags = FNM_PATHNAME | FNM_LEADING_DIR | 427156230Smux FNM_PREFIX_DIRS; 428156230Smux for (i = 0; i < pattlist_size(coll->co_accepts); i++) { 429156230Smux pat = pattlist_get(coll->co_accepts, i); 430156230Smux diraccept = globtree_or(diraccept, 431156230Smux globtree_match(pat, flags)); 432156230Smux 433156230Smux len = strlen(pat); 434156230Smux if (coll->co_options & CO_CHECKOUTMODE && 435156230Smux (len == 0 || pat[len - 1] != '*')) { 436156230Smux /* We must modify the pattern so that it 437156230Smux refers to the RCS file, rather than 438156230Smux the checked-out file. */ 439156230Smux xasprintf(&pat, "%s,v", pat); 440156230Smux fileaccept = globtree_or(fileaccept, 441156230Smux globtree_match(pat, flags)); 442156230Smux free(pat); 443156230Smux } else { 444156230Smux fileaccept = globtree_or(fileaccept, 445156230Smux globtree_match(pat, flags)); 446156230Smux } 447156230Smux } 448156230Smux } 449156230Smux 450156230Smux for (i = 0; i < pattlist_size(coll->co_refusals); i++) { 451156230Smux pat = pattlist_get(coll->co_refusals, i); 452156230Smux dirrefuse = globtree_or(dirrefuse, 453156230Smux globtree_match(pat, 0)); 454156230Smux len = strlen(pat); 455156230Smux if (coll->co_options & CO_CHECKOUTMODE && 456156230Smux (len == 0 || pat[len - 1] != '*')) { 457156230Smux /* We must modify the pattern so that it refers 458156230Smux to the RCS file, rather than the checked-out 459156230Smux file. */ 460156230Smux xasprintf(&pat, "%s,v", pat); 461156230Smux filerefuse = globtree_or(filerefuse, 462156230Smux globtree_match(pat, 0)); 463156230Smux free(pat); 464156230Smux } else { 465156230Smux filerefuse = globtree_or(filerefuse, 466156230Smux globtree_match(pat, 0)); 467156230Smux } 468156230Smux } 469156230Smux 470156230Smux coll->co_dirfilter = globtree_and(diraccept, 471156230Smux globtree_not(dirrefuse)); 472156230Smux coll->co_filefilter = globtree_and(fileaccept, 473156230Smux globtree_not(filerefuse)); 474156230Smux 475156230Smux /* Set up a mask of file attributes that we don't want to sync 476156230Smux with the server. */ 477156230Smux if (!(coll->co_options & CO_SETOWNER)) 478156230Smux coll->co_attrignore |= FA_OWNER | FA_GROUP; 479156230Smux if (!(coll->co_options & CO_SETMODE)) 480156230Smux coll->co_attrignore |= FA_MODE; 481156230Smux if (!(coll->co_options & CO_SETFLAGS)) 482156230Smux coll->co_attrignore |= FA_FLAGS; 483156230Smux } 484156230Smux return (STATUS_SUCCESS); 485156230Smuxbad: 486156230Smux lprintf(-1, "Protocol error during collection exchange\n"); 487156230Smux return (STATUS_FAILURE); 488156230Smux} 489156230Smux 490156230Smuxstatic struct mux * 491156230Smuxproto_mux(struct config *config) 492156230Smux{ 493156230Smux struct mux *m; 494156230Smux struct stream *s, *wr; 495156230Smux struct chan *chan0, *chan1; 496156230Smux int id; 497156230Smux 498156230Smux s = config->server; 499156230Smux lprintf(2, "Establishing multiplexed-mode data connection\n"); 500156230Smux proto_printf(s, "MUX\n"); 501156230Smux stream_flush(s); 502156230Smux m = mux_open(config->socket, &chan0); 503156230Smux if (m == NULL) { 504156230Smux lprintf(-1, "Cannot open the multiplexer\n"); 505156230Smux return (NULL); 506156230Smux } 507156230Smux id = chan_listen(m); 508156230Smux if (id == -1) { 509156230Smux lprintf(-1, "ChannelMux.Listen failed: %s\n", strerror(errno)); 510156230Smux mux_close(m); 511156230Smux return (NULL); 512156230Smux } 513156230Smux wr = stream_open(chan0, NULL, (stream_writefn_t *)chan_write, NULL); 514156230Smux proto_printf(wr, "CHAN %d\n", id); 515156230Smux stream_close(wr); 516156230Smux chan1 = chan_accept(m, id); 517156230Smux if (chan1 == NULL) { 518156230Smux lprintf(-1, "ChannelMux.Accept failed: %s\n", strerror(errno)); 519156230Smux mux_close(m); 520156230Smux return (NULL); 521156230Smux } 522156230Smux config->chan0 = chan0; 523156230Smux config->chan1 = chan1; 524156230Smux return (m); 525156230Smux} 526156230Smux 527156230Smux/* 528156230Smux * Initializes the connection to the CVSup server, that is handle 529156230Smux * the protocol negotiation, logging in, exchanging file attributes 530156230Smux * support and collections information, and finally run the update 531156230Smux * session. 532156230Smux */ 533156230Smuxint 534156230Smuxproto_run(struct config *config) 535156230Smux{ 536156230Smux struct thread_args lister_args; 537156230Smux struct thread_args detailer_args; 538156230Smux struct thread_args updater_args; 539156230Smux struct thread_args *args; 540156230Smux struct killer killer; 541156230Smux struct threads *workers; 542156230Smux struct mux *m; 543156230Smux int i, status; 544156230Smux 545156230Smux /* 546156230Smux * We pass NULL for the close() function because we'll reuse 547156230Smux * the socket after the stream is closed. 548156230Smux */ 549156230Smux config->server = stream_open_fd(config->socket, stream_read_fd, 550156230Smux stream_write_fd, NULL); 551156230Smux status = proto_greet(config); 552156230Smux if (status == STATUS_SUCCESS) 553156230Smux status = proto_negproto(config); 554156230Smux if (status == STATUS_SUCCESS) 555203368Slulf status = auth_login(config); 556156230Smux if (status == STATUS_SUCCESS) 557156230Smux status = proto_fileattr(config); 558156230Smux if (status == STATUS_SUCCESS) 559156230Smux status = proto_xchgcoll(config); 560156230Smux if (status != STATUS_SUCCESS) 561156230Smux return (status); 562156230Smux 563156230Smux /* Multi-threaded action starts here. */ 564156230Smux m = proto_mux(config); 565156230Smux if (m == NULL) 566156230Smux return (STATUS_FAILURE); 567156230Smux 568156230Smux stream_close(config->server); 569156230Smux config->server = NULL; 570156230Smux config->fixups = fixups_new(); 571156230Smux killer_start(&killer, m); 572156230Smux 573156230Smux /* Start the worker threads. */ 574156230Smux workers = threads_new(); 575156230Smux args = &lister_args; 576156230Smux args->config = config; 577156230Smux args->status = -1; 578156230Smux args->errmsg = NULL; 579156230Smux args->rd = NULL; 580156230Smux args->wr = stream_open(config->chan0, 581156230Smux NULL, (stream_writefn_t *)chan_write, NULL); 582156230Smux threads_create(workers, lister, args); 583156230Smux 584156230Smux args = &detailer_args; 585156230Smux args->config = config; 586156230Smux args->status = -1; 587156230Smux args->errmsg = NULL; 588156230Smux args->rd = stream_open(config->chan0, 589156230Smux (stream_readfn_t *)chan_read, NULL, NULL); 590156230Smux args->wr = stream_open(config->chan1, 591156230Smux NULL, (stream_writefn_t *)chan_write, NULL); 592156230Smux threads_create(workers, detailer, args); 593156230Smux 594156230Smux args = &updater_args; 595156230Smux args->config = config; 596156230Smux args->status = -1; 597156230Smux args->errmsg = NULL; 598156230Smux args->rd = stream_open(config->chan1, 599156230Smux (stream_readfn_t *)chan_read, NULL, NULL); 600156230Smux args->wr = NULL; 601156230Smux threads_create(workers, updater, args); 602156230Smux 603156230Smux lprintf(2, "Running\n"); 604156230Smux /* Wait for all the worker threads to finish. */ 605156230Smux status = STATUS_SUCCESS; 606156230Smux for (i = 0; i < 3; i++) { 607156230Smux args = threads_wait(workers); 608156230Smux if (args->rd != NULL) 609156230Smux stream_close(args->rd); 610156230Smux if (args->wr != NULL) 611156230Smux stream_close(args->wr); 612156230Smux if (args->status != STATUS_SUCCESS) { 613156230Smux assert(args->errmsg != NULL); 614156230Smux if (status == STATUS_SUCCESS) { 615156230Smux status = args->status; 616156230Smux /* Shutdown the multiplexer to wake up all 617156230Smux the other threads. */ 618156230Smux mux_shutdown(m, args->errmsg, status); 619156230Smux } 620156230Smux free(args->errmsg); 621156230Smux } 622156230Smux } 623156230Smux threads_free(workers); 624156230Smux if (status == STATUS_SUCCESS) { 625156230Smux lprintf(2, "Shutting down connection to server\n"); 626156230Smux chan_close(config->chan0); 627156230Smux chan_close(config->chan1); 628156230Smux chan_wait(config->chan0); 629156230Smux chan_wait(config->chan1); 630156230Smux mux_shutdown(m, NULL, STATUS_SUCCESS); 631156230Smux } 632156230Smux killer_stop(&killer); 633156230Smux fixups_free(config->fixups); 634156230Smux status = mux_close(m); 635156230Smux if (status == STATUS_SUCCESS) { 636156230Smux lprintf(1, "Finished successfully\n"); 637156230Smux } else if (status == STATUS_INTERRUPTED) { 638156230Smux lprintf(-1, "Interrupted\n"); 639156230Smux if (killer.killedby != -1) 640156230Smux kill(getpid(), killer.killedby); 641156230Smux } 642156230Smux return (status); 643156230Smux} 644156230Smux 645156230Smux/* 646156230Smux * Write a string into the stream, escaping characters as needed. 647156230Smux * Characters escaped: 648156230Smux * 649156230Smux * SPACE -> "\_" 650156230Smux * TAB -> "\t" 651156230Smux * NEWLINE -> "\n" 652156230Smux * CR -> "\r" 653156230Smux * \ -> "\\" 654156230Smux */ 655156230Smuxstatic int 656156230Smuxproto_escape(struct stream *wr, const char *s) 657156230Smux{ 658156230Smux size_t len; 659156230Smux ssize_t n; 660156230Smux char c; 661156230Smux 662156230Smux /* Handle characters that need escaping. */ 663156230Smux do { 664156230Smux len = strcspn(s, " \t\r\n\\"); 665156230Smux n = stream_write(wr, s, len); 666156230Smux if (n == -1) 667156230Smux return (-1); 668156230Smux c = s[len]; 669156230Smux switch (c) { 670156230Smux case ' ': 671156230Smux n = stream_write(wr, "\\_", 2); 672156230Smux break; 673156230Smux case '\t': 674156230Smux n = stream_write(wr, "\\t", 2); 675156230Smux break; 676156230Smux case '\r': 677156230Smux n = stream_write(wr, "\\r", 2); 678156230Smux break; 679156230Smux case '\n': 680156230Smux n = stream_write(wr, "\\n", 2); 681156230Smux break; 682156230Smux case '\\': 683156230Smux n = stream_write(wr, "\\\\", 2); 684156230Smux break; 685156230Smux } 686156230Smux if (n == -1) 687156230Smux return (-1); 688156230Smux s += len + 1; 689156230Smux } while (c != '\0'); 690156230Smux return (0); 691156230Smux} 692156230Smux 693156230Smux/* 694156230Smux * A simple printf() implementation specifically tailored for csup. 695156230Smux * List of the supported formats: 696156230Smux * 697156230Smux * %c Print a char. 698156230Smux * %d or %i Print an int as decimal. 699156230Smux * %x Print an int as hexadecimal. 700156230Smux * %o Print an int as octal. 701156230Smux * %t Print a time_t as decimal. 702156230Smux * %s Print a char * escaping some characters as needed. 703156230Smux * %S Print a char * without escaping. 704156230Smux * %f Print an encoded struct fattr *. 705156230Smux * %F Print an encoded struct fattr *, specifying the supported 706156230Smux * attributes. 707156230Smux */ 708156230Smuxint 709156230Smuxproto_printf(struct stream *wr, const char *format, ...) 710156230Smux{ 711156230Smux fattr_support_t *support; 712156230Smux long long longval; 713156230Smux struct fattr *fa; 714156230Smux const char *fmt; 715156230Smux va_list ap; 716156230Smux char *cp, *s, *attr; 717156230Smux ssize_t n; 718186781Slulf size_t size; 719186781Slulf off_t off; 720156230Smux int rv, val, ignore; 721156230Smux char c; 722156230Smux 723156230Smux n = 0; 724156230Smux rv = 0; 725156230Smux fmt = format; 726156230Smux va_start(ap, format); 727156230Smux while ((cp = strchr(fmt, '%')) != NULL) { 728156230Smux if (cp > fmt) { 729156230Smux n = stream_write(wr, fmt, cp - fmt); 730241049Skevlo if (n == -1) { 731241049Skevlo va_end(ap); 732156230Smux return (-1); 733241049Skevlo } 734156230Smux } 735156230Smux if (*++cp == '\0') 736156230Smux goto done; 737156230Smux switch (*cp) { 738156230Smux case 'c': 739156230Smux c = va_arg(ap, int); 740156230Smux rv = stream_printf(wr, "%c", c); 741156230Smux break; 742156230Smux case 'd': 743156230Smux case 'i': 744156230Smux val = va_arg(ap, int); 745156230Smux rv = stream_printf(wr, "%d", val); 746156230Smux break; 747156230Smux case 'x': 748156230Smux val = va_arg(ap, int); 749156230Smux rv = stream_printf(wr, "%x", val); 750156230Smux break; 751156230Smux case 'o': 752156230Smux val = va_arg(ap, int); 753156230Smux rv = stream_printf(wr, "%o", val); 754156230Smux break; 755186781Slulf case 'O': 756186781Slulf off = va_arg(ap, off_t); 757228626Sdim rv = stream_printf(wr, "%" PRId64, off); 758186781Slulf break; 759156230Smux case 'S': 760156230Smux s = va_arg(ap, char *); 761156230Smux assert(s != NULL); 762156230Smux rv = stream_printf(wr, "%s", s); 763156230Smux break; 764156230Smux case 's': 765156230Smux s = va_arg(ap, char *); 766156230Smux assert(s != NULL); 767156230Smux rv = proto_escape(wr, s); 768156230Smux break; 769156230Smux case 't': 770156230Smux longval = (long long)va_arg(ap, time_t); 771156230Smux rv = stream_printf(wr, "%lld", longval); 772156230Smux break; 773156230Smux case 'f': 774156230Smux fa = va_arg(ap, struct fattr *); 775156230Smux attr = fattr_encode(fa, NULL, 0); 776156230Smux rv = proto_escape(wr, attr); 777156230Smux free(attr); 778156230Smux break; 779156230Smux case 'F': 780156230Smux fa = va_arg(ap, struct fattr *); 781156230Smux support = va_arg(ap, fattr_support_t *); 782156230Smux ignore = va_arg(ap, int); 783156230Smux attr = fattr_encode(fa, *support, ignore); 784156230Smux rv = proto_escape(wr, attr); 785156230Smux free(attr); 786156230Smux break; 787186781Slulf case 'z': 788186781Slulf size = va_arg(ap, size_t); 789186781Slulf rv = stream_printf(wr, "%zu", size); 790186781Slulf break; 791186781Slulf 792156230Smux case '%': 793156230Smux n = stream_write(wr, "%", 1); 794241021Skevlo if (n == -1) { 795241021Skevlo va_end(ap); 796156230Smux return (-1); 797241021Skevlo } 798156230Smux break; 799156230Smux } 800241021Skevlo if (rv == -1) { 801241021Skevlo va_end(ap); 802156230Smux return (-1); 803241021Skevlo } 804156230Smux fmt = cp + 1; 805156230Smux } 806156230Smux if (*fmt != '\0') { 807156230Smux rv = stream_printf(wr, "%s", fmt); 808241021Skevlo if (rv == -1) { 809241021Skevlo va_end(ap); 810156230Smux return (-1); 811241021Skevlo } 812156230Smux } 813156230Smuxdone: 814156230Smux va_end(ap); 815156230Smux return (0); 816156230Smux} 817156230Smux 818156230Smux/* 819156230Smux * Unescape the string, see proto_escape(). 820156230Smux */ 821156230Smuxstatic void 822156230Smuxproto_unescape(char *s) 823156230Smux{ 824156230Smux char *cp, *cp2; 825156230Smux 826156230Smux cp = s; 827156230Smux while ((cp = strchr(cp, '\\')) != NULL) { 828156230Smux switch (cp[1]) { 829156230Smux case '_': 830156230Smux *cp = ' '; 831156230Smux break; 832156230Smux case 't': 833156230Smux *cp = '\t'; 834156230Smux break; 835156230Smux case 'r': 836156230Smux *cp = '\r'; 837156230Smux break; 838156230Smux case 'n': 839156230Smux *cp = '\n'; 840156230Smux break; 841156230Smux case '\\': 842156230Smux *cp = '\\'; 843156230Smux break; 844156230Smux default: 845156230Smux *cp = *(cp + 1); 846156230Smux } 847156230Smux cp2 = ++cp; 848156230Smux while (*cp2 != '\0') { 849156230Smux *cp2 = *(cp2 + 1); 850156230Smux cp2++; 851156230Smux } 852156230Smux } 853156230Smux} 854156230Smux 855156230Smux/* 856156230Smux * Get an ascii token in the string. 857156230Smux */ 858156230Smuxchar * 859156230Smuxproto_get_ascii(char **s) 860156230Smux{ 861156230Smux char *ret; 862156230Smux 863156230Smux ret = strsep(s, " "); 864156230Smux if (ret == NULL) 865156230Smux return (NULL); 866156230Smux /* Make sure we disallow 0-length fields. */ 867156230Smux if (*ret == '\0') { 868156230Smux *s = NULL; 869156230Smux return (NULL); 870156230Smux } 871156230Smux proto_unescape(ret); 872156230Smux return (ret); 873156230Smux} 874156230Smux 875156230Smux/* 876156230Smux * Get the rest of the string. 877156230Smux */ 878156230Smuxchar * 879156230Smuxproto_get_rest(char **s) 880156230Smux{ 881156230Smux char *ret; 882156230Smux 883156230Smux if (s == NULL) 884156230Smux return (NULL); 885156230Smux ret = *s; 886156230Smux proto_unescape(ret); 887156230Smux *s = NULL; 888156230Smux return (ret); 889156230Smux} 890156230Smux 891156230Smux/* 892156230Smux * Get an int token. 893156230Smux */ 894156230Smuxint 895156230Smuxproto_get_int(char **s, int *val, int base) 896156230Smux{ 897156701Smux char *cp; 898156701Smux int error; 899156230Smux 900156230Smux cp = proto_get_ascii(s); 901156230Smux if (cp == NULL) 902156230Smux return (-1); 903156701Smux error = asciitoint(cp, val, base); 904156701Smux return (error); 905156230Smux} 906156230Smux 907156230Smux/* 908186781Slulf * Get a size_t token. 909186781Slulf */ 910186781Slulfint 911186781Slulfproto_get_sizet(char **s, size_t *val, int base) 912186781Slulf{ 913186781Slulf unsigned long long tmp; 914186781Slulf char *cp, *end; 915186781Slulf 916186781Slulf cp = proto_get_ascii(s); 917186781Slulf if (cp == NULL) 918186781Slulf return (-1); 919186781Slulf errno = 0; 920186781Slulf tmp = strtoll(cp, &end, base); 921186781Slulf if (errno || *end != '\0') 922186781Slulf return (-1); 923186781Slulf *val = (size_t)tmp; 924186781Slulf return (0); 925186781Slulf} 926186781Slulf 927186781Slulf/* 928156230Smux * Get a time_t token. 929156230Smux * 930156230Smux * Ideally, we would use an intmax_t and strtoimax() here, but strtoll() 931156230Smux * is more portable and 64bits should be enough for a timestamp. 932156230Smux */ 933156230Smuxint 934156230Smuxproto_get_time(char **s, time_t *val) 935156230Smux{ 936156230Smux long long tmp; 937156230Smux char *cp, *end; 938156230Smux 939156230Smux cp = proto_get_ascii(s); 940156230Smux if (cp == NULL) 941156230Smux return (-1); 942156230Smux errno = 0; 943156230Smux tmp = strtoll(cp, &end, 10); 944156230Smux if (errno || *end != '\0') 945156230Smux return (-1); 946156230Smux *val = (time_t)tmp; 947156230Smux return (0); 948156230Smux} 949156230Smux 950156230Smux/* Start the killer thread. It is used to protect against some signals 951156230Smux during the multi-threaded run so that we can gracefully fail. */ 952156230Smuxstatic void 953156230Smuxkiller_start(struct killer *k, struct mux *m) 954156230Smux{ 955156230Smux int error; 956156230Smux 957156230Smux k->mux = m; 958156230Smux k->killedby = -1; 959156230Smux sigemptyset(&k->sigset); 960156230Smux sigaddset(&k->sigset, SIGINT); 961156230Smux sigaddset(&k->sigset, SIGHUP); 962156230Smux sigaddset(&k->sigset, SIGTERM); 963156230Smux sigaddset(&k->sigset, SIGPIPE); 964156230Smux pthread_sigmask(SIG_BLOCK, &k->sigset, NULL); 965156230Smux error = pthread_create(&k->thread, NULL, killer_run, k); 966156230Smux if (error) 967156230Smux err(1, "pthread_create"); 968156230Smux} 969156230Smux 970156230Smux/* The main loop of the killer thread. */ 971156230Smuxstatic void * 972156230Smuxkiller_run(void *arg) 973156230Smux{ 974156230Smux struct killer *k; 975156230Smux int error, sig, old; 976156230Smux 977156230Smux k = arg; 978156230Smuxagain: 979156230Smux error = sigwait(&k->sigset, &sig); 980156230Smux assert(!error); 981156230Smux if (sig == SIGINT || sig == SIGHUP || sig == SIGTERM) { 982156230Smux if (k->killedby == -1) { 983156230Smux k->killedby = sig; 984156230Smux /* Ensure we don't get canceled during the shutdown. */ 985156230Smux pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old); 986156230Smux mux_shutdown(k->mux, "Cleaning up ...", 987156230Smux STATUS_INTERRUPTED); 988156230Smux pthread_setcancelstate(old, NULL); 989156230Smux } 990156230Smux } 991156230Smux goto again; 992156230Smux} 993156230Smux 994156230Smux/* Stop the killer thread. */ 995156230Smuxstatic void 996156230Smuxkiller_stop(struct killer *k) 997156230Smux{ 998156230Smux void *val; 999156230Smux int error; 1000156230Smux 1001156230Smux error = pthread_cancel(k->thread); 1002156230Smux assert(!error); 1003156230Smux pthread_join(k->thread, &val); 1004156230Smux assert(val == PTHREAD_CANCELED); 1005156230Smux pthread_sigmask(SIG_UNBLOCK, &k->sigset, NULL); 1006156230Smux} 1007