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