xref: /openbsd-src/usr.bin/cvs/client.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: client.c,v 1.56 2007/01/28 02:04:45 joris Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include "includes.h"
19 
20 #include "cvs.h"
21 #include "log.h"
22 #include "diff.h"
23 #include "remote.h"
24 
25 struct cvs_req cvs_requests[] = {
26 	/* this is what our client will use, the server should support it */
27 	{ "Root",		1,	cvs_server_root, REQ_NEEDED },
28 	{ "Valid-responses",	1,	cvs_server_validresp, REQ_NEEDED },
29 	{ "valid-requests",	1,	cvs_server_validreq, REQ_NEEDED },
30 	{ "Directory",		0,	cvs_server_directory, REQ_NEEDED },
31 	{ "Static-directory",	0,	cvs_server_static_directory, REQ_NEEDED },
32 	{ "Sticky",		0,	cvs_server_sticky, REQ_NEEDED },
33 	{ "Entry",		0,	cvs_server_entry, REQ_NEEDED },
34 	{ "Modified",		0,	cvs_server_modified, REQ_NEEDED },
35 	{ "UseUnchanged",	0,	cvs_server_useunchanged, REQ_NEEDED },
36 	{ "Unchanged",		0,	cvs_server_unchanged, REQ_NEEDED },
37 	{ "Questionable",	0,	cvs_server_questionable, REQ_NEEDED },
38 	{ "Argument",		0,	cvs_server_argument, REQ_NEEDED },
39 	{ "Argumentx",		0,	cvs_server_argumentx, REQ_NEEDED },
40 	{ "Global_option",	0,	cvs_server_globalopt, REQ_NEEDED },
41 	{ "Set",		0,	cvs_server_set, REQ_NEEDED },
42 
43 	/*
44 	 * used to tell the server what is going on in our
45 	 * working copy, unsupported until we are told otherwise
46 	 */
47 	{ "Max-dotdot",			0,	NULL, 0 },
48 	{ "Checkin-prog",		0,	NULL, 0 },
49 	{ "Update-prog",		0,	NULL, 0 },
50 	{ "Kopt",			0,	NULL, 0 },
51 	{ "Checkin-time",		0,	NULL, 0 },
52 	{ "Is-modified",		0,	NULL, 0 },
53 	{ "Notify",			0,	NULL, 0 },
54 	{ "Case",			0,	NULL, 0 },
55 	{ "Gzip-stream",		0,	NULL, 0 },
56 	{ "wrapper-sendme-rcsOptions",	0,	NULL, 0 },
57 	{ "Kerberos-encrypt",		0,	NULL, 0 },
58 	{ "Gssapi-encrypt",		0,	NULL, 0 },
59 	{ "Gssapi-authenticate",	0,	NULL, 0 },
60 	{ "expand-modules",		0,	NULL, 0 },
61 
62 	/* commands that might be supported */
63 	{ "ci",				0,	cvs_server_commit, 0 },
64 	{ "co",				0,	cvs_server_checkout, 0 },
65 	{ "update",			0,	cvs_server_update, 0 },
66 	{ "diff",			0,	cvs_server_diff, 0 },
67 	{ "log",			0,	cvs_server_log, 0 },
68 	{ "rlog",			0,	NULL, 0 },
69 	{ "add",			0,	cvs_server_add, 0 },
70 	{ "remove",			0,	cvs_server_remove, 0 },
71 	{ "update-patches",		0,	cvs_server_update_patches, 0 },
72 	{ "gzip-file-contents",		0,	NULL, 0 },
73 	{ "status",			0,	cvs_server_status, 0 },
74 	{ "rdiff",			0,	NULL, 0 },
75 	{ "tag",			0,	cvs_server_tag, 0 },
76 	{ "rtag",			0,	NULL, 0 },
77 	{ "import",			0,	cvs_server_import, 0 },
78 	{ "admin",			0,	cvs_server_admin, 0 },
79 	{ "export",			0,	NULL, 0 },
80 	{ "history",			0,	NULL, 0 },
81 	{ "release",			0,	NULL, 0 },
82 	{ "watch-on",			0,	NULL, 0 },
83 	{ "watch-off",			0,	NULL, 0 },
84 	{ "watch-add",			0,	NULL, 0 },
85 	{ "watch-remove",		0,	NULL, 0 },
86 	{ "watchers",			0,	NULL, 0 },
87 	{ "editors",			0,	NULL, 0 },
88 	{ "init",			0,	cvs_server_init, 0 },
89 	{ "annotate",			0,	cvs_server_annotate, 0 },
90 	{ "rannotate",			0,	NULL, 0 },
91 	{ "noop",			0,	NULL, 0 },
92 	{ "version",			0,	cvs_server_version, 0 },
93 	{ "",				-1,	NULL, 0 }
94 };
95 
96 static void	 client_check_directory(char *);
97 static char	*client_get_supported_responses(void);
98 static char	*lastdir = NULL;
99 static int	 end_of_response = 0;
100 
101 static void	cvs_client_initlog(void);
102 
103 /*
104  * File descriptors for protocol logging when the CVS_CLIENT_LOG environment
105  * variable is set.
106  */
107 static int	cvs_client_logon = 0;
108 int	cvs_client_inlog_fd = -1;
109 int	cvs_client_outlog_fd = -1;
110 
111 
112 int server_response = SERVER_OK;
113 
114 static char *
115 client_get_supported_responses(void)
116 {
117 	BUF *bp;
118 	char *d;
119 	int i, first;
120 
121 	first = 0;
122 	bp = cvs_buf_alloc(512, BUF_AUTOEXT);
123 	for (i = 0; cvs_responses[i].supported != -1; i++) {
124 		if (cvs_responses[i].hdlr == NULL)
125 			continue;
126 
127 		if (first != 0)
128 			cvs_buf_append(bp, " ", 1);
129 		else
130 			first++;
131 		cvs_buf_append(bp, cvs_responses[i].name,
132 		    strlen(cvs_responses[i].name));
133 	}
134 
135 	cvs_buf_putc(bp, '\0');
136 	d = cvs_buf_release(bp);
137 	return (d);
138 }
139 
140 static void
141 client_check_directory(char *data)
142 {
143 	int l;
144 	CVSENTRIES *entlist;
145 	char entry[CVS_ENT_MAXLINELEN], *parent, *base;
146 
147 	STRIP_SLASH(data);
148 
149 	cvs_mkpath(data);
150 
151 	if ((base = basename(data)) == NULL)
152 		fatal("client_check_directory: overflow");
153 
154 	if ((parent = dirname(data)) == NULL)
155 		fatal("client_check_directory: overflow");
156 
157 	if (!strcmp(parent, "."))
158 		return;
159 
160 	l = snprintf(entry, CVS_ENT_MAXLINELEN, "D/%s////", base);
161 	if (l == -1 || l >= CVS_ENT_MAXLINELEN)
162 		fatal("client_check_directory: overflow");
163 
164 	entlist = cvs_ent_open(parent);
165 	cvs_ent_add(entlist, entry);
166 	cvs_ent_close(entlist, ENT_SYNC);
167 }
168 
169 void
170 cvs_client_connect_to_server(void)
171 {
172 	struct cvs_var *vp;
173 	char *cmd, *argv[9], *resp;
174 	int ifd[2], ofd[2], argc;
175 
176 	if (cvs_server_active == 1)
177 		fatal("cvs_client_connect: I was already connected to server");
178 
179 	switch (current_cvsroot->cr_method) {
180 	case CVS_METHOD_PSERVER:
181 	case CVS_METHOD_KSERVER:
182 	case CVS_METHOD_GSERVER:
183 	case CVS_METHOD_FORK:
184 	case CVS_METHOD_EXT:
185 		fatal("the specified connection method is not supported");
186 	default:
187 		break;
188 	}
189 
190 	if (pipe(ifd) == -1)
191 		fatal("cvs_client_connect: %s", strerror(errno));
192 	if (pipe(ofd) == -1)
193 		fatal("cvs_client_connect: %s", strerror(errno));
194 
195 	switch (fork()) {
196 	case -1:
197 		fatal("cvs_client_connect: fork failed: %s", strerror(errno));
198 	case 0:
199 		if (dup2(ifd[0], STDIN_FILENO) == -1)
200 			fatal("cvs_client_connect: %s", strerror(errno));
201 		if (dup2(ofd[1], STDOUT_FILENO) == -1)
202 			fatal("cvs_client_connect: %s", strerror(errno));
203 
204 		close(ifd[1]);
205 		close(ofd[0]);
206 
207 		if ((cmd = getenv("CVS_SERVER")) == NULL)
208 			cmd = CVS_SERVER_DEFAULT;
209 
210 		argc = 0;
211 		argv[argc++] = cvs_rsh;
212 
213 		if (current_cvsroot->cr_user != NULL) {
214 			argv[argc++] = "-l";
215 			argv[argc++] = current_cvsroot->cr_user;
216 		}
217 
218 		argv[argc++] = current_cvsroot->cr_host;
219 		argv[argc++] = cmd;
220 		argv[argc++] = "server";
221 		argv[argc] = NULL;
222 
223 		cvs_log(LP_TRACE, "connecting to server %s",
224 		    current_cvsroot->cr_host);
225 
226 		execvp(argv[0], argv);
227 		fatal("cvs_client_connect: failed to execute cvs server");
228 	default:
229 		break;
230 	}
231 
232 	close(ifd[0]);
233 	close(ofd[1]);
234 
235 	if ((current_cvsroot->cr_srvin = fdopen(ifd[1], "w")) == NULL)
236 		fatal("cvs_client_connect: %s", strerror(errno));
237 	if ((current_cvsroot->cr_srvout = fdopen(ofd[0], "r")) == NULL)
238 		fatal("cvs_client_connect: %s", strerror(errno));
239 
240 	setvbuf(current_cvsroot->cr_srvin, NULL,_IOLBF, 0);
241 	setvbuf(current_cvsroot->cr_srvout, NULL, _IOLBF, 0);
242 
243 	cvs_client_initlog();
244 
245 	if (cvs_cmdop != CVS_OP_INIT)
246 		cvs_client_send_request("Root %s", current_cvsroot->cr_dir);
247 
248 	resp = client_get_supported_responses();
249 	cvs_client_send_request("Valid-responses %s", resp);
250 	xfree(resp);
251 
252 	cvs_client_send_request("valid-requests");
253 	cvs_client_get_responses();
254 
255 	cvs_client_send_request("UseUnchanged");
256 
257 	if (cvs_nolog == 1)
258 		cvs_client_send_request("Global_option -l");
259 
260 	if (cvs_noexec == 1)
261 		cvs_client_send_request("Global_option -n");
262 
263 	if (verbosity == 0)
264 		cvs_client_send_request("Global_option -Q");
265 
266 	/* Be quiet. This is the default in OpenCVS. */
267 	cvs_client_send_request("Global_option -q");
268 
269 	if (cvs_readonly == 1)
270 		cvs_client_send_request("Global_option -r");
271 
272 	if (cvs_trace == 1)
273 		cvs_client_send_request("Global_option -t");
274 
275 	if (verbosity == 2)
276 		cvs_client_send_request("Global_option -V");
277 
278 	/* XXX: If 'Set' is supported? */
279 	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
280 		cvs_client_send_request("Set %s=%s", vp->cv_name, vp->cv_val);
281 }
282 
283 void
284 cvs_client_send_request(char *fmt, ...)
285 {
286 	va_list ap;
287 	char *data, *s;
288 	struct cvs_req *req;
289 
290 	va_start(ap, fmt);
291 	vasprintf(&data, fmt, ap);
292 	va_end(ap);
293 
294 	if ((s = strchr(data, ' ')) != NULL)
295 		*s = '\0';
296 
297 	req = cvs_remote_get_request_info(data);
298 	if (req == NULL)
299 		fatal("'%s' is an unknown request", data);
300 
301 	if (req->supported != 1)
302 		fatal("remote cvs server does not support '%s'", data);
303 
304 	if (s != NULL)
305 		*s = ' ';
306 
307 	cvs_log(LP_TRACE, "%s", data);
308 
309 	if (cvs_client_inlog_fd != -1) {
310 		BUF *bp;
311 
312 		bp = cvs_buf_alloc(strlen(data), BUF_AUTOEXT);
313 
314 		if (cvs_buf_append(bp, data, strlen(data)) < 0)
315 			fatal("cvs_client_send_request: cvs_buf_append");
316 
317 		cvs_buf_putc(bp, '\n');
318 
319 		if (cvs_buf_write_fd(bp, cvs_client_inlog_fd) < 0)
320 			fatal("cvs_client_send_request: cvs_buf_write_fd");
321 
322 		cvs_buf_free(bp);
323 	}
324 
325 	cvs_remote_output(data);
326 	xfree(data);
327 }
328 
329 void
330 cvs_client_read_response(void)
331 {
332 	char *cmd, *data;
333 	struct cvs_resp *resp;
334 
335 	cmd = cvs_remote_input();
336 	if ((data = strchr(cmd, ' ')) != NULL)
337 		(*data++) = '\0';
338 
339 	resp = cvs_remote_get_response_info(cmd);
340 	if (resp == NULL)
341 		fatal("response '%s' is not supported by our client", cmd);
342 
343 	if (resp->hdlr == NULL)
344 		fatal("opencvs client does not support '%s'", cmd);
345 
346 	(*resp->hdlr)(data);
347 
348 	xfree(cmd);
349 }
350 
351 void
352 cvs_client_get_responses(void)
353 {
354 	while (end_of_response != 1)
355 		cvs_client_read_response();
356 
357 	end_of_response = 0;
358 }
359 
360 void
361 cvs_client_senddir(const char *dir)
362 {
363 	struct stat st;
364 	int nb;
365 	char *d, *date, fpath[MAXPATHLEN], repo[MAXPATHLEN], *tag;
366 
367 	d = NULL;
368 
369 	if (lastdir != NULL && !strcmp(dir, lastdir))
370 		return;
371 
372 	cvs_get_repository_path(dir, repo, MAXPATHLEN);
373 
374 	cvs_client_send_request("Directory %s\n%s", dir, repo);
375 
376 	if (cvs_path_cat(dir, CVS_PATH_STATICENTRIES, fpath, MAXPATHLEN) >=
377 	    MAXPATHLEN)
378 		fatal("cvs_client_senddir: truncation");
379 
380 	if (stat(fpath, &st) == 0 && (st.st_mode & (S_IRUSR|S_IRGRP|S_IROTH)))
381 		cvs_client_send_request("Static-directory");
382 
383 	d = xstrdup(dir);
384 	cvs_parse_tagfile(d, &tag, &date, &nb);
385 
386 	if (tag != NULL || date != NULL) {
387 		char buf[128];
388 
389 		if (tag != NULL && nb != NULL) {
390 			if (strlcpy(buf, "N", sizeof(buf)) >= sizeof(buf))
391 				fatal("cvs_client_senddir: truncation");
392 		} else if (tag != NULL) {
393 			if (strlcpy(buf, "T", sizeof(buf)) >= sizeof(buf))
394 				fatal("cvs_client_senddir: truncation");
395 		} else {
396 			if (strlcpy(buf, "D", sizeof(buf)) >= sizeof(buf))
397 				fatal("cvs_client_senddir: truncation");
398 		}
399 
400 		if (strlcat(buf, tag ? tag : date, sizeof(buf)) >= sizeof(buf))
401 			fatal("cvs_client_senddir: truncation");
402 
403 		cvs_client_send_request("Sticky %s", buf);
404 
405 		if (tag != NULL)
406 			xfree(tag);
407 		if (date != NULL)
408 			xfree(date);
409 	}
410 	if (d != NULL)
411 		xfree(d);
412 
413 	if (lastdir != NULL)
414 		xfree(lastdir);
415 	lastdir = xstrdup(dir);
416 }
417 
418 void
419 cvs_client_sendfile(struct cvs_file *cf)
420 {
421 	int l;
422 	size_t len;
423 	char rev[16], timebuf[64], sticky[32];
424 
425 	if (cf->file_type != CVS_FILE)
426 		return;
427 
428 	cvs_client_senddir(cf->file_wd);
429 	cvs_remote_classify_file(cf);
430 
431 	if (cf->file_type == CVS_DIR)
432 		return;
433 
434 	if (cf->file_ent != NULL) {
435 		if (cf->file_status == FILE_ADDED) {
436 			len = strlcpy(rev, "0", sizeof(rev));
437 			if (len >= sizeof(rev))
438 				fatal("cvs_client_sendfile: truncation");
439 
440 			len = strlcpy(timebuf, "Initial ", sizeof(timebuf));
441 			if (len >= sizeof(timebuf))
442 				fatal("cvs_client_sendfile: truncation");
443 
444 			len = strlcat(timebuf, cf->file_name, sizeof(timebuf));
445 			if (len >= sizeof(timebuf))
446 				fatal("cvs_client_sendfile: truncation");
447 		} else {
448 			rcsnum_tostr(cf->file_ent->ce_rev, rev, sizeof(rev));
449 			ctime_r(&cf->file_ent->ce_mtime, timebuf);
450 		}
451 
452 		if (cf->file_ent->ce_conflict == NULL) {
453 			if (timebuf[strlen(timebuf) - 1] == '\n')
454 				timebuf[strlen(timebuf) - 1] = '\0';
455 		} else {
456 			len = strlcpy(timebuf, cf->file_ent->ce_conflict,
457 			    sizeof(timebuf));
458 			if (len >= sizeof(timebuf))
459 				fatal("cvs_client_sendfile: truncation");
460 			len = strlcat(timebuf, "+", sizeof(timebuf));
461 			if (len >= sizeof(timebuf))
462 				fatal("cvs_client_sendfile: truncation");
463 		}
464 
465 		sticky[0] = '\0';
466 		if (cf->file_ent->ce_tag != NULL) {
467 			l = snprintf(sticky, sizeof(sticky), "T%s",
468 			    cf->file_ent->ce_tag);
469 			if (l == -1 || l >= (int)sizeof(sticky))
470 				fatal("cvs_client_sendfile: overflow");
471 		}
472 
473 		cvs_client_send_request("Entry /%s/%s%s/%s/%s/%s",
474 		    cf->file_name, (cf->file_status == FILE_REMOVED) ? "-" : "",
475 		    rev, timebuf, cf->file_ent->ce_opts ?
476 		    cf->file_ent->ce_opts : "", sticky);
477 	}
478 
479 	switch (cf->file_status) {
480 	case FILE_UNKNOWN:
481 		if (cf->fd != -1)
482 			cvs_client_send_request("Questionable %s",
483 			    cf->file_name);
484 		break;
485 	case FILE_ADDED:
486 	case FILE_MODIFIED:
487 		cvs_client_send_request("Modified %s", cf->file_name);
488 		cvs_remote_send_file(cf->file_path);
489 		break;
490 	case FILE_UPTODATE:
491 		cvs_client_send_request("Unchanged %s", cf->file_name);
492 		break;
493 	}
494 }
495 
496 void
497 cvs_client_send_files(char **argv, int argc)
498 {
499 	int i;
500 
501 	for (i = 0; i < argc; i++)
502 		cvs_client_send_request("Argument %s", argv[i]);
503 }
504 
505 void
506 cvs_client_ok(char *data)
507 {
508 	end_of_response = 1;
509 	server_response = SERVER_OK;
510 }
511 
512 void
513 cvs_client_error(char *data)
514 {
515 	end_of_response = 1;
516 	server_response = SERVER_ERROR;
517 }
518 
519 void
520 cvs_client_validreq(char *data)
521 {
522 	int i;
523 	char *sp, *ep;
524 	struct cvs_req *req;
525 
526 	sp = data;
527 	do {
528 		if ((ep = strchr(sp, ' ')) != NULL)
529 			*ep = '\0';
530 
531 		req = cvs_remote_get_request_info(sp);
532 		if (req != NULL)
533 			req->supported = 1;
534 
535 		if (ep != NULL)
536 			sp = ep + 1;
537 	} while (ep != NULL);
538 
539 	for (i = 0; cvs_requests[i].supported != -1; i++) {
540 		req = &cvs_requests[i];
541 		if ((req->flags & REQ_NEEDED) &&
542 		    req->supported != 1) {
543 			fatal("server does not support required '%s'",
544 			    req->name);
545 		}
546 	}
547 }
548 
549 void
550 cvs_client_e(char *data)
551 {
552 	cvs_printf("%s\n", data);
553 }
554 
555 void
556 cvs_client_m(char *data)
557 {
558 	cvs_printf("%s\n", data);
559 }
560 
561 void
562 cvs_client_checkedin(char *data)
563 {
564 	int l;
565 	CVSENTRIES *entlist;
566 	struct cvs_ent *ent, *newent;
567 	char *dir, *e, entry[CVS_ENT_MAXLINELEN], rev[16], timebuf[64];
568 	char sticky[16];
569 
570 	dir = cvs_remote_input();
571 	e = cvs_remote_input();
572 	xfree(dir);
573 
574 	entlist = cvs_ent_open(data);
575 	newent = cvs_ent_parse(e);
576 	ent = cvs_ent_get(entlist, newent->ce_name);
577 	xfree(e);
578 
579 	rcsnum_tostr(newent->ce_rev, rev, sizeof(rev));
580 	ctime_r(&ent->ce_mtime, timebuf);
581 	if (timebuf[strlen(timebuf) - 1] == '\n')
582 		timebuf[strlen(timebuf) - 1] = '\0';
583 
584 	sticky[0] = '\0';
585 	if (ent->ce_tag != NULL) {
586 		l = snprintf(sticky, sizeof(sticky), "T%s", ent->ce_tag);
587 		if (l == -1 || l >= (int)sizeof(sticky))
588 			fatal("cvs_client_checkedin: overflow");
589 	}
590 
591 	l = snprintf(entry, CVS_ENT_MAXLINELEN, "/%s/%s%s/%s/%s/%s",
592 	    newent->ce_name, (newent->ce_status == CVS_ENT_REMOVED) ? "-" : "",
593 	    rev, timebuf, ent->ce_opts ? ent->ce_opts : "", sticky);
594 	if (l == -1 || l >= CVS_ENT_MAXLINELEN)
595 		fatal("cvs_client_checkedin: overflow");
596 
597 	cvs_ent_free(ent);
598 	cvs_ent_free(newent);
599 	cvs_ent_add(entlist, entry);
600 	cvs_ent_close(entlist, ENT_SYNC);
601 }
602 
603 void
604 cvs_client_updated(char *data)
605 {
606 	int l, fd;
607 	time_t now;
608 	mode_t fmode, mask;
609 	size_t flen;
610 	CVSENTRIES *ent;
611 	struct cvs_ent *e;
612 	const char *errstr;
613 	struct timeval tv[2];
614 	char timebuf[32], repo[MAXPATHLEN], *rpath, entry[CVS_ENT_MAXLINELEN];
615 	char *en, *mode, revbuf[32], *len, *fpath, *wdir;
616 
617 	client_check_directory(data);
618 
619 	rpath = cvs_remote_input();
620 	en = cvs_remote_input();
621 	mode = cvs_remote_input();
622 	len = cvs_remote_input();
623 
624 	cvs_get_repository_path(".", repo, MAXPATHLEN);
625 
626 	STRIP_SLASH(repo);
627 
628 	if (strlen(repo) + 1 > strlen(rpath))
629 		fatal("received a repository path that is too short");
630 
631 	fpath = rpath + strlen(repo) + 1;
632 	if ((wdir = dirname(fpath)) == NULL)
633 		fatal("cvs_client_updated: dirname: %s", strerror(errno));
634 
635 	flen = strtonum(len, 0, INT_MAX, &errstr);
636 	if (errstr != NULL)
637 		fatal("cvs_client_updated: %s: %s", len, errstr);
638 	xfree(len);
639 
640 	cvs_strtomode(mode, &fmode);
641 	xfree(mode);
642 	mask = umask(0);
643 	umask(mask);
644 	fmode &= ~mask;
645 
646 	time(&now);
647 	asctime_r(gmtime(&now), timebuf);
648 	if (timebuf[strlen(timebuf) - 1] == '\n')
649 		timebuf[strlen(timebuf) - 1] = '\0';
650 
651 	e = cvs_ent_parse(en);
652 	xfree(en);
653 	rcsnum_tostr(e->ce_rev, revbuf, sizeof(revbuf));
654 	l = snprintf(entry, CVS_ENT_MAXLINELEN, "/%s/%s/%s/%s/", e->ce_name,
655 	    revbuf, timebuf, e->ce_opts ? e->ce_opts : "");
656 	if (l == -1 || l >= CVS_ENT_MAXLINELEN)
657 		fatal("cvs_client_updated: overflow");
658 
659 	cvs_ent_free(e);
660 	ent = cvs_ent_open(wdir);
661 	cvs_ent_add(ent, entry);
662 	cvs_ent_close(ent, ENT_SYNC);
663 
664 	if ((fd = open(fpath, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
665 		fatal("cvs_client_updated: open: %s: %s",
666 		    fpath, strerror(errno));
667 
668 	cvs_remote_receive_file(fd, flen);
669 
670 	tv[0].tv_sec = now;
671 	tv[0].tv_usec = 0;
672 	tv[1] = tv[0];
673 
674 	if (futimes(fd, tv) == -1)
675 		fatal("cvs_client_updated: futimes: %s", strerror(errno));
676 
677 	if (fchmod(fd, fmode) == -1)
678 		fatal("cvs_client_updated: fchmod: %s", strerror(errno));
679 
680 	(void)close(fd);
681 
682 	xfree(rpath);
683 }
684 
685 void
686 cvs_client_merged(char *data)
687 {
688 	int fd;
689 	time_t now;
690 	mode_t fmode;
691 	size_t flen;
692 	CVSENTRIES *ent;
693 	const char *errstr;
694 	struct timeval tv[2];
695 	char timebuf[32], *repo, *rpath, *entry, *mode;
696 	char *len, *fpath, *wdir;
697 
698 	client_check_directory(data);
699 
700 	rpath = cvs_remote_input();
701 	entry = cvs_remote_input();
702 	mode = cvs_remote_input();
703 	len = cvs_remote_input();
704 
705 	repo = xmalloc(MAXPATHLEN);
706 	cvs_get_repository_path(".", repo, MAXPATHLEN);
707 
708 	STRIP_SLASH(repo);
709 
710 	if (strlen(repo) + 1 > strlen(rpath))
711 		fatal("received a repository path that is too short");
712 
713 	fpath = rpath + strlen(repo) + 1;
714 	if ((wdir = dirname(fpath)) == NULL)
715 		fatal("cvs_client_merged: dirname: %s", strerror(errno));
716 	xfree(repo);
717 
718 	flen = strtonum(len, 0, INT_MAX, &errstr);
719 	if (errstr != NULL)
720 		fatal("cvs_client_merged: %s: %s", len, errstr);
721 	xfree(len);
722 
723 	cvs_strtomode(mode, &fmode);
724 	xfree(mode);
725 
726 	time(&now);
727 	asctime_r(gmtime(&now), timebuf);
728 	if (timebuf[strlen(timebuf) - 1] == '\n')
729 		timebuf[strlen(timebuf) - 1] = '\0';
730 
731 	ent = cvs_ent_open(wdir);
732 	cvs_ent_add(ent, entry);
733 	cvs_ent_close(ent, ENT_SYNC);
734 
735 	if ((fd = open(fpath, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
736 		fatal("cvs_client_merged: open: %s: %s",
737 		    fpath, strerror(errno));
738 
739 	cvs_remote_receive_file(fd, flen);
740 
741 	tv[0].tv_sec = now;
742 	tv[0].tv_usec = 0;
743 	tv[1] = tv[0];
744 
745 	if (futimes(fd, tv) == -1)
746 		fatal("cvs_client_merged: futimes: %s", strerror(errno));
747 
748 	if (fchmod(fd, fmode) == -1)
749 		fatal("cvs_client_merged: fchmod: %s", strerror(errno));
750 
751 	(void)close(fd);
752 
753 	xfree(rpath);
754 }
755 
756 void
757 cvs_client_removed(char *data)
758 {
759 	char *dir;
760 
761 	dir = cvs_remote_input();
762 	xfree(dir);
763 }
764 
765 void
766 cvs_client_remove_entry(char *data)
767 {
768 	CVSENTRIES *entlist;
769 	char *filename, *rpath;
770 
771 	rpath = cvs_remote_input();
772 	if ((filename = strrchr(rpath, '/')) == NULL)
773 		fatal("bad rpath in cvs_client_remove_entry: %s", rpath);
774 	*filename++;
775 
776 	entlist = cvs_ent_open(data);
777 	cvs_ent_remove(entlist, filename);
778 	cvs_ent_close(entlist, ENT_SYNC);
779 
780 	xfree(rpath);
781 }
782 
783 void
784 cvs_client_set_static_directory(char *data)
785 {
786 	FILE *fp;
787 	char *dir, fpath[MAXPATHLEN];
788 
789 	if (cvs_cmdop == CVS_OP_EXPORT)
790 		return;
791 
792 	STRIP_SLASH(data);
793 
794 	dir = cvs_remote_input();
795 	xfree(dir);
796 
797 	if (cvs_path_cat(data, CVS_PATH_STATICENTRIES, fpath, MAXPATHLEN) >=
798 	    MAXPATHLEN)
799 		fatal("cvs_client_set_static_directory: truncation");
800 
801 	if ((fp = fopen(fpath, "w+")) == NULL) {
802 		cvs_log(LP_ERRNO, "%s", fpath);
803 		return;
804 	}
805 	(void)fclose(fp);
806 }
807 
808 void
809 cvs_client_clear_static_directory(char *data)
810 {
811 	char *dir, fpath[MAXPATHLEN];
812 
813 	if (cvs_cmdop == CVS_OP_EXPORT)
814 		return;
815 
816 	STRIP_SLASH(data);
817 
818 	dir = cvs_remote_input();
819 	xfree(dir);
820 
821 	if (cvs_path_cat(data, CVS_PATH_STATICENTRIES, fpath, MAXPATHLEN) >=
822 	    MAXPATHLEN)
823 		fatal("cvs_client_clear_static_directory: truncation");
824 
825 	(void)cvs_unlink(fpath);
826 }
827 
828 void
829 cvs_client_set_sticky(char *data)
830 {
831 	FILE *fp;
832 	char *dir, *tag, tagpath[MAXPATHLEN];
833 
834 	if (cvs_cmdop == CVS_OP_EXPORT)
835 		return;
836 
837 	STRIP_SLASH(data);
838 
839 	dir = cvs_remote_input();
840 	xfree(dir);
841 	tag = cvs_remote_input();
842 
843 	if (cvs_path_cat(data, CVS_PATH_TAG, tagpath, MAXPATHLEN) >= MAXPATHLEN)
844 		fatal("cvs_client_clear_sticky: truncation");
845 
846 	if ((fp = fopen(tagpath, "w+")) == NULL) {
847 		cvs_log(LP_ERRNO, "%s", tagpath);
848 		goto out;
849 	}
850 
851 	(void)fprintf(fp, "%s\n", tag);
852 	(void)fclose(fp);
853 out:
854 	xfree(tag);
855 }
856 
857 void
858 cvs_client_clear_sticky(char *data)
859 {
860 	char *dir, tagpath[MAXPATHLEN];
861 
862 	if (cvs_cmdop == CVS_OP_EXPORT)
863 		return;
864 
865 	STRIP_SLASH(data);
866 
867 	dir = cvs_remote_input();
868 	xfree(dir);
869 
870 	if (cvs_path_cat(data, CVS_PATH_TAG, tagpath, MAXPATHLEN) >= MAXPATHLEN)
871 		fatal("cvs_client_clear_sticky: truncation");
872 
873 	(void)cvs_unlink(tagpath);
874 }
875 
876 
877 /*
878  * cvs_client_initlog()
879  *
880  * Initialize protocol logging if the CVS_CLIENT_LOG environment variable is
881  * set.  In this case, the variable's value is used as a path to which the
882  * appropriate suffix is added (".in" for client input and ".out" for server
883  * output).
884  */
885 static void
886 cvs_client_initlog(void)
887 {
888 	int l;
889 	u_int i;
890 	char *env, *envdup, buf[MAXPATHLEN], fpath[MAXPATHLEN];
891 	char rpath[MAXPATHLEN], *s;
892 	struct stat st;
893 	time_t now;
894 	struct passwd *pwd;
895 
896 	/* avoid doing it more than once */
897 	if (cvs_client_logon)
898 		return;
899 
900 	if ((env = getenv("CVS_CLIENT_LOG")) == NULL)
901 		return;
902 
903 	envdup = xstrdup(env);
904 	if ((s = strchr(envdup, '%')) != NULL)
905 		*s = '\0';
906 
907 	if (strlcpy(buf, env, sizeof(buf)) >= sizeof(buf))
908 		fatal("cvs_client_initlog: truncation");
909 
910 	if (strlcpy(rpath, envdup, sizeof(rpath)) >= sizeof(rpath))
911 		fatal("cvs_client_initlog: truncation");
912 
913 	xfree(envdup);
914 
915 	s = buf;
916 	while ((s = strchr(s, '%')) != NULL) {
917 		s++;
918 		switch (*s) {
919 		case 'c':
920 			if (strlcpy(fpath, cvs_command, sizeof(fpath)) >=
921 			    sizeof(fpath))
922 				fatal("cvs_client_initlog: truncation");
923 			break;
924 		case 'd':
925 			time(&now);
926 			if (strlcpy(fpath, ctime(&now), sizeof(fpath)) >=
927 			    sizeof(fpath))
928 				fatal("cvs_client_initlog: truncation");
929 			break;
930 		case 'p':
931 			snprintf(fpath, sizeof(fpath), "%d", getpid());
932 			break;
933 		case 'u':
934 			if ((pwd = getpwuid(getuid())) != NULL) {
935 				if (strlcpy(fpath, pwd->pw_name,
936 				    sizeof(fpath)) >= sizeof(fpath))
937 					fatal("cvs_client_initlog: truncation");
938 			} else {
939 				fpath[0] = '\0';
940 			}
941 			endpwent();
942 			break;
943 		default:
944 			fpath[0] = '\0';
945 			break;
946 		}
947 
948 		if (fpath[0] != '\0') {
949 			if (strlcat(rpath, "-", sizeof(rpath)) >= sizeof(rpath))
950 				fatal("cvs_client_initlog: truncation");
951 
952 			if (strlcat(rpath, fpath, sizeof(rpath))
953 			    >= sizeof(rpath))
954 				fatal("cvs_client_initlog: truncation");
955 		}
956 	}
957 
958 	for (i = 0; i < UINT_MAX; i++) {
959 		l = snprintf(fpath, sizeof(fpath), "%s-%d.in", rpath, i);
960 		if (l == -1 || l >= (int)sizeof(fpath))
961 			fatal("cvs_client_initlog: overflow");
962 
963 		if (stat(fpath, &st) != -1)
964 			continue;
965 
966 		if (errno != ENOENT)
967 			fatal("cvs_client_initlog() stat failed '%s'",
968 			    strerror(errno));
969 
970 		break;
971 	}
972 
973 	if ((cvs_client_inlog_fd = open(fpath,
974 	    O_RDWR | O_CREAT | O_TRUNC, 0644)) == NULL) {
975 		fatal("cvs_client_initlog: open `%s': %s",
976 		    fpath, strerror(errno));
977 	}
978 
979 	for (i = 0; i < UINT_MAX; i++) {
980 		l = snprintf(fpath, sizeof(fpath), "%s-%d.out", rpath, i);
981 		if (l == -1 || l >= (int)sizeof(fpath))
982 			fatal("cvs_client_initlog: overflow");
983 
984 		if (stat(fpath, &st) != -1)
985 			continue;
986 
987 		if (errno != ENOENT)
988 			fatal("cvs_client_initlog() stat failed '%s'",
989 			    strerror(errno));
990 
991 		break;
992 	}
993 
994 	if ((cvs_client_outlog_fd = open(fpath,
995 	    O_RDWR | O_CREAT | O_TRUNC, 0644)) == NULL) {
996 		fatal("cvs_client_initlog: open `%s': %s",
997 		    fpath, strerror(errno));
998 	}
999 
1000 	cvs_client_logon = 1;
1001 }
1002