xref: /openbsd-src/usr.bin/cvs/server.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: server.c,v 1.99 2009/04/18 16:26:01 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 <sys/types.h>
19 #include <sys/stat.h>
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <libgen.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "cvs.h"
30 #include "remote.h"
31 
32 struct cvs_resp cvs_responses[] = {
33 	/* this is what our server uses, the client should support it */
34 	{ "Valid-requests",	1,	cvs_client_validreq, RESP_NEEDED },
35 	{ "ok",			0,	cvs_client_ok, RESP_NEEDED},
36 	{ "error",		0,	cvs_client_error, RESP_NEEDED },
37 	{ "E",			0,	cvs_client_e, RESP_NEEDED },
38 	{ "M",			0,	cvs_client_m, RESP_NEEDED },
39 	{ "Checked-in",		0,	cvs_client_checkedin, RESP_NEEDED },
40 	{ "Updated",		0,	cvs_client_updated, RESP_NEEDED },
41 	{ "Merged",		0,	cvs_client_merged, RESP_NEEDED },
42 	{ "Removed",		0,	cvs_client_removed, RESP_NEEDED },
43 	{ "Remove-entry",	0,	cvs_client_remove_entry, 0 },
44 	{ "Set-static-directory",	0,
45 	    cvs_client_set_static_directory, 0 },
46 	{ "Clear-static-directory",	0,
47 	    cvs_client_clear_static_directory, 0 },
48 	{ "Set-sticky",		0,	cvs_client_set_sticky, 0 },
49 	{ "Clear-sticky",	0,	cvs_client_clear_sticky, 0 },
50 
51 	/* unsupported responses until told otherwise */
52 	{ "New-entry",			0,	NULL, 0 },
53 	{ "Created",			0,	NULL, 0 },
54 	{ "Update-existing",		0,	NULL, 0 },
55 	{ "Rcs-diff",			0,	NULL, 0 },
56 	{ "Patched",			0,	NULL, 0 },
57 	{ "Mode",			0,	NULL, 0 },
58 	{ "Mod-time",			0,	NULL, 0 },
59 	{ "Checksum",			0,	NULL, 0 },
60 	{ "Copy-file",			0,	NULL, 0 },
61 	{ "Template",			0,	NULL, 0 },
62 	{ "Set-checkin-prog",		0,	NULL, 0 },
63 	{ "Set-update-prog",		0,	NULL, 0 },
64 	{ "Notified",			0,	NULL, 0 },
65 	{ "Module-expansion",		0,	NULL, 0 },
66 	{ "Wrapper-rcsOption",		0,	NULL, 0 },
67 	{ "Mbinary",			0,	NULL, 0 },
68 	{ "F",				0,	NULL, 0 },
69 	{ "MT",				0,	NULL, 0 },
70 	{ "",				-1,	NULL, 0 }
71 };
72 
73 int	cvs_server(int, char **);
74 char	*cvs_server_path = NULL;
75 
76 static char *server_currentdir = NULL;
77 static char **server_argv;
78 static int server_argc = 1;
79 
80 extern int disable_fast_checkout;
81 
82 struct cvs_cmd cvs_cmd_server = {
83 	CVS_OP_SERVER, CVS_USE_WDIR, "server", { "", "" },
84 	"server mode",
85 	NULL,
86 	NULL,
87 	NULL,
88 	cvs_server
89 };
90 
91 
92 int
93 cvs_server(int argc, char **argv)
94 {
95 	char *cmd, *data;
96 	struct cvs_req *req;
97 
98 	if (argc > 1)
99 		fatal("server does not take any extra arguments");
100 
101 	/* Be on server-side very verbose per default. */
102 	verbosity = 2;
103 
104 	setvbuf(stdin, NULL, _IOLBF, 0);
105 	setvbuf(stdout, NULL, _IOLBF, 0);
106 
107 	cvs_server_active = 1;
108 
109 	server_argv = xcalloc(server_argc + 1, sizeof(*server_argv));
110 	server_argv[0] = xstrdup("server");
111 
112 	(void)xasprintf(&cvs_server_path, "%s/cvs-serv%d", cvs_tmpdir,
113 	    getpid());
114 
115 	if (mkdir(cvs_server_path, 0700) == -1)
116 		fatal("failed to create temporary server directory: %s, %s",
117 		    cvs_server_path, strerror(errno));
118 
119 	if (chdir(cvs_server_path) == -1)
120 		fatal("failed to change directory to '%s'", cvs_server_path);
121 
122 	for (;;) {
123 		cmd = cvs_remote_input();
124 
125 		if ((data = strchr(cmd, ' ')) != NULL)
126 			(*data++) = '\0';
127 
128 		req = cvs_remote_get_request_info(cmd);
129 		if (req == NULL)
130 			fatal("request '%s' is not supported by our server",
131 			    cmd);
132 
133 		if (req->hdlr == NULL)
134 			fatal("opencvs server does not support '%s'", cmd);
135 
136 		if ((req->flags & REQ_NEEDDIR) && (server_currentdir == NULL))
137 			fatal("`%s' needs a directory to be sent with "
138 			    "the `Directory` request first", cmd);
139 
140 		(*req->hdlr)(data);
141 		xfree(cmd);
142 	}
143 
144 	return (0);
145 }
146 
147 void
148 cvs_server_send_response(char *fmt, ...)
149 {
150 	int i;
151 	va_list ap;
152 	char *data;
153 
154 	va_start(ap, fmt);
155 	i = vasprintf(&data, fmt, ap);
156 	va_end(ap);
157 	if (i == -1)
158 		fatal("cvs_server_send_response: could not allocate memory");
159 
160 	cvs_log(LP_TRACE, "%s", data);
161 	cvs_remote_output(data);
162 	xfree(data);
163 }
164 
165 void
166 cvs_server_root(char *data)
167 {
168 	if (data == NULL)
169 		fatal("Missing argument for Root");
170 
171 	if (current_cvsroot != NULL)
172 		return;
173 
174 	if (data[0] != '/' || (current_cvsroot = cvsroot_get(data)) == NULL)
175 		fatal("Invalid Root specified!");
176 
177 	cvs_parse_configfile();
178 	cvs_parse_modules();
179 	umask(cvs_umask);
180 }
181 
182 void
183 cvs_server_validresp(char *data)
184 {
185 	int i;
186 	char *sp, *ep;
187 	struct cvs_resp *resp;
188 
189 	if ((sp = data) == NULL)
190 		fatal("Missing argument for Valid-responses");
191 
192 	do {
193 		if ((ep = strchr(sp, ' ')) != NULL)
194 			*ep = '\0';
195 
196 		resp = cvs_remote_get_response_info(sp);
197 		if (resp != NULL)
198 			resp->supported = 1;
199 
200 		if (ep != NULL)
201 			sp = ep + 1;
202 	} while (ep != NULL);
203 
204 	for (i = 0; cvs_responses[i].supported != -1; i++) {
205 		resp = &cvs_responses[i];
206 		if ((resp->flags & RESP_NEEDED) &&
207 		    resp->supported != 1) {
208 			fatal("client does not support required '%s'",
209 			    resp->name);
210 		}
211 	}
212 }
213 
214 void
215 cvs_server_validreq(char *data)
216 {
217 	BUF *bp;
218 	char *d;
219 	int i, first;
220 
221 	first = 0;
222 	bp = cvs_buf_alloc(512);
223 	for (i = 0; cvs_requests[i].supported != -1; i++) {
224 		if (cvs_requests[i].hdlr == NULL)
225 			continue;
226 
227 		if (first != 0)
228 			cvs_buf_putc(bp, ' ');
229 		else
230 			first++;
231 
232 		cvs_buf_puts(bp, cvs_requests[i].name);
233 	}
234 
235 	cvs_buf_putc(bp, '\0');
236 	d = cvs_buf_release(bp);
237 
238 	cvs_server_send_response("Valid-requests %s", d);
239 	cvs_server_send_response("ok");
240 	xfree(d);
241 }
242 
243 void
244 cvs_server_static_directory(char *data)
245 {
246 	FILE *fp;
247 	char fpath[MAXPATHLEN];
248 
249 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/%s",
250 	    server_currentdir, CVS_PATH_STATICENTRIES);
251 
252 	if ((fp = fopen(fpath, "w+")) == NULL) {
253 		cvs_log(LP_ERRNO, "%s", fpath);
254 		return;
255 	}
256 	(void)fclose(fp);
257 }
258 
259 void
260 cvs_server_sticky(char *data)
261 {
262 	FILE *fp;
263 	char tagpath[MAXPATHLEN];
264 
265 	if (data == NULL)
266 		fatal("Missing argument for Sticky");
267 
268 	(void)xsnprintf(tagpath, MAXPATHLEN, "%s/%s",
269 	    server_currentdir, CVS_PATH_TAG);
270 
271 	if ((fp = fopen(tagpath, "w+")) == NULL) {
272 		cvs_log(LP_ERRNO, "%s", tagpath);
273 		return;
274 	}
275 
276 	(void)fprintf(fp, "%s\n", data);
277 	(void)fclose(fp);
278 }
279 
280 void
281 cvs_server_globalopt(char *data)
282 {
283 	if (data == NULL)
284 		fatal("Missing argument for Global_option");
285 
286 	if (!strcmp(data, "-l"))
287 		cvs_nolog = 1;
288 
289 	if (!strcmp(data, "-n"))
290 		cvs_noexec = 1;
291 
292 	if (!strcmp(data, "-Q"))
293 		verbosity = 0;
294 
295 	if (!strcmp(data, "-q"))
296 		verbosity = 1;
297 
298 	if (!strcmp(data, "-r"))
299 		cvs_readonly = 1;
300 
301 	if (!strcmp(data, "-t"))
302 		cvs_trace = 1;
303 }
304 
305 void
306 cvs_server_set(char *data)
307 {
308 	char *ep;
309 
310 	if (data == NULL)
311 		fatal("Missing argument for Set");
312 
313 	ep = strchr(data, '=');
314 	if (ep == NULL)
315 		fatal("no = in variable assignment");
316 
317 	*(ep++) = '\0';
318 	if (cvs_var_set(data, ep) < 0)
319 		fatal("cvs_server_set: cvs_var_set failed");
320 }
321 
322 void
323 cvs_server_directory(char *data)
324 {
325 	CVSENTRIES *entlist;
326 	char *dir, *repo, *parent, *entry, *dirn, *p;
327 
328 	if (current_cvsroot == NULL)
329 		fatal("No Root specified for Directory");
330 
331 	dir = cvs_remote_input();
332 	STRIP_SLASH(dir);
333 
334 	if (strlen(dir) < strlen(current_cvsroot->cr_dir))
335 		fatal("cvs_server_directory: bad Directory request");
336 
337 	repo = dir + strlen(current_cvsroot->cr_dir);
338 
339 	/*
340 	 * This is somewhat required for checkout, as the
341 	 * directory request will be:
342 	 *
343 	 * Directory .
344 	 * /path/to/cvs/root
345 	 */
346 	if (repo[0] == '\0')
347 		p = xstrdup(".");
348 	else
349 		p = xstrdup(repo + 1);
350 
351 	cvs_mkpath(p, NULL);
352 
353 	if ((dirn = basename(p)) == NULL)
354 		fatal("cvs_server_directory: %s", strerror(errno));
355 
356 	if ((parent = dirname(p)) == NULL)
357 		fatal("cvs_server_directory: %s", strerror(errno));
358 
359 	if (strcmp(parent, ".")) {
360 		entry = xmalloc(CVS_ENT_MAXLINELEN);
361 		cvs_ent_line_str(dirn, NULL, NULL, NULL, NULL, 1, 0,
362 		    entry, CVS_ENT_MAXLINELEN);
363 
364 		entlist = cvs_ent_open(parent);
365 		cvs_ent_add(entlist, entry);
366 		xfree(entry);
367 	}
368 
369 	if (server_currentdir != NULL)
370 		xfree(server_currentdir);
371 	server_currentdir = p;
372 
373 	xfree(dir);
374 }
375 
376 void
377 cvs_server_entry(char *data)
378 {
379 	CVSENTRIES *entlist;
380 
381 	if (data == NULL)
382 		fatal("Missing argument for Entry");
383 
384 	entlist = cvs_ent_open(server_currentdir);
385 	cvs_ent_add(entlist, data);
386 }
387 
388 void
389 cvs_server_modified(char *data)
390 {
391 	int fd;
392 	size_t flen;
393 	mode_t fmode;
394 	const char *errstr;
395 	char *mode, *len, fpath[MAXPATHLEN];
396 
397 	if (data == NULL)
398 		fatal("Missing argument for Modified");
399 
400 	/* sorry, we have to use TMP_DIR */
401 	disable_fast_checkout = 1;
402 
403 	mode = cvs_remote_input();
404 	len = cvs_remote_input();
405 
406 	cvs_strtomode(mode, &fmode);
407 	xfree(mode);
408 
409 	flen = strtonum(len, 0, INT_MAX, &errstr);
410 	if (errstr != NULL)
411 		fatal("cvs_server_modified: %s", errstr);
412 	xfree(len);
413 
414 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", server_currentdir, data);
415 
416 	if ((fd = open(fpath, O_WRONLY | O_CREAT | O_TRUNC)) == -1)
417 		fatal("cvs_server_modified: %s: %s", fpath, strerror(errno));
418 
419 	cvs_remote_receive_file(fd, flen);
420 
421 	if (fchmod(fd, 0600) == -1)
422 		fatal("cvs_server_modified: failed to set file mode");
423 
424 	(void)close(fd);
425 }
426 
427 void
428 cvs_server_useunchanged(char *data)
429 {
430 }
431 
432 void
433 cvs_server_unchanged(char *data)
434 {
435 	char fpath[MAXPATHLEN];
436 	CVSENTRIES *entlist;
437 	struct cvs_ent *ent;
438 	char sticky[CVS_ENT_MAXLINELEN];
439 	char rev[CVS_REV_BUFSZ], entry[CVS_ENT_MAXLINELEN];
440 
441 	if (data == NULL)
442 		fatal("Missing argument for Unchanged");
443 
444 	/* sorry, we have to use TMP_DIR */
445 	disable_fast_checkout = 1;
446 
447 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", server_currentdir, data);
448 
449 	entlist = cvs_ent_open(server_currentdir);
450 	ent = cvs_ent_get(entlist, data);
451 	if (ent == NULL)
452 		fatal("received Unchanged request for non-existing file");
453 
454 	sticky[0] = '\0';
455 	if (ent->ce_tag != NULL)
456 		(void)xsnprintf(sticky, sizeof(sticky), "T%s", ent->ce_tag);
457 
458 	rcsnum_tostr(ent->ce_rev, rev, sizeof(rev));
459 	(void)xsnprintf(entry, sizeof(entry), "/%s/%s/%s/%s/%s",
460 	    ent->ce_name, rev, CVS_SERVER_UNCHANGED, ent->ce_opts ?
461 	    ent->ce_opts : "", sticky);
462 
463 	cvs_ent_free(ent);
464 	cvs_ent_add(entlist, entry);
465 }
466 
467 void
468 cvs_server_questionable(char *data)
469 {
470 	CVSENTRIES *entlist;
471 	char entry[CVS_ENT_MAXLINELEN];
472 
473 	if (data == NULL)
474 		fatal("Questionable request with no data attached");
475 
476 	(void)xsnprintf(entry, sizeof(entry), "/%s/%c///", data,
477 	    CVS_SERVER_QUESTIONABLE);
478 
479 	entlist = cvs_ent_open(server_currentdir);
480 	cvs_ent_add(entlist, entry);
481 
482 	/* sorry, we have to use TMP_DIR */
483 	disable_fast_checkout = 1;
484 }
485 
486 void
487 cvs_server_argument(char *data)
488 {
489 	if (data == NULL)
490 		fatal("Missing argument for Argument");
491 
492 	server_argv = xrealloc(server_argv, server_argc + 2,
493 	    sizeof(*server_argv));
494 	server_argv[server_argc] = xstrdup(data);
495 	server_argv[++server_argc] = NULL;
496 }
497 
498 void
499 cvs_server_argumentx(char *data)
500 {
501 	int idx;
502 	size_t len;
503 
504 	if (server_argc == 1)
505 		fatal("Protocol Error: ArgumentX without previous argument");
506 
507 	idx = server_argc - 1;
508 
509 	len = strlen(server_argv[idx]) + strlen(data) + 2;
510 	server_argv[idx] = xrealloc(server_argv[idx], len, sizeof(char));
511 	strlcat(server_argv[idx], "\n", len);
512 	strlcat(server_argv[idx], data, len);
513 }
514 
515 void
516 cvs_server_update_patches(char *data)
517 {
518 	/*
519 	 * This does not actually do anything.
520 	 * It is used to tell that the server is able to
521 	 * generate patches when given an `update' request.
522 	 * The client must issue the -u argument to `update'
523 	 * to receive patches.
524 	 */
525 }
526 
527 void
528 cvs_server_add(char *data)
529 {
530 	if (chdir(server_currentdir) == -1)
531 		fatal("cvs_server_add: %s", strerror(errno));
532 
533 	cvs_cmdop = CVS_OP_ADD;
534 	cmdp->cmd_flags = cvs_cmd_add.cmd_flags;
535 	cvs_add(server_argc, server_argv);
536 	cvs_server_send_response("ok");
537 }
538 
539 void
540 cvs_server_import(char *data)
541 {
542 	if (chdir(server_currentdir) == -1)
543 		fatal("cvs_server_import: %s", strerror(errno));
544 
545 	cvs_cmdop = CVS_OP_IMPORT;
546 	cmdp->cmd_flags = cvs_cmd_import.cmd_flags;
547 	cvs_import(server_argc, server_argv);
548 	cvs_server_send_response("ok");
549 }
550 
551 void
552 cvs_server_admin(char *data)
553 {
554 	if (chdir(server_currentdir) == -1)
555 		fatal("cvs_server_admin: %s", strerror(errno));
556 
557 	cvs_cmdop = CVS_OP_ADMIN;
558 	cmdp->cmd_flags = cvs_cmd_admin.cmd_flags;
559 	cvs_admin(server_argc, server_argv);
560 	cvs_server_send_response("ok");
561 }
562 
563 void
564 cvs_server_annotate(char *data)
565 {
566 	if (chdir(server_currentdir) == -1)
567 		fatal("cvs_server_annotate: %s", strerror(errno));
568 
569 	cvs_cmdop = CVS_OP_ANNOTATE;
570 	cmdp->cmd_flags = cvs_cmd_annotate.cmd_flags;
571 	cvs_annotate(server_argc, server_argv);
572 	cvs_server_send_response("ok");
573 }
574 
575 void
576 cvs_server_rannotate(char *data)
577 {
578 	if (chdir(server_currentdir) == -1)
579 		fatal("cvs_server_rannotate: %s", strerror(errno));
580 
581 	cvs_cmdop = CVS_OP_RANNOTATE;
582 	cmdp->cmd_flags = cvs_cmd_rannotate.cmd_flags;
583 	cvs_annotate(server_argc, server_argv);
584 	cvs_server_send_response("ok");
585 }
586 
587 void
588 cvs_server_commit(char *data)
589 {
590 	if (chdir(server_currentdir) == -1)
591 		fatal("cvs_server_commit: %s", strerror(errno));
592 
593 	cvs_cmdop = CVS_OP_COMMIT;
594 	cmdp->cmd_flags = cvs_cmd_commit.cmd_flags;
595 	cvs_commit(server_argc, server_argv);
596 	cvs_server_send_response("ok");
597 }
598 
599 void
600 cvs_server_checkout(char *data)
601 {
602 	if (chdir(server_currentdir) == -1)
603 		fatal("cvs_server_checkout: %s", strerror(errno));
604 
605 	cvs_cmdop = CVS_OP_CHECKOUT;
606 	cmdp->cmd_flags = cvs_cmd_checkout.cmd_flags;
607 	cvs_checkout(server_argc, server_argv);
608 	cvs_server_send_response("ok");
609 }
610 
611 void
612 cvs_server_diff(char *data)
613 {
614 	if (chdir(server_currentdir) == -1)
615 		fatal("cvs_server_diff: %s", strerror(errno));
616 
617 	cvs_cmdop = CVS_OP_DIFF;
618 	cmdp->cmd_flags = cvs_cmd_diff.cmd_flags;
619 	cvs_diff(server_argc, server_argv);
620 	cvs_server_send_response("ok");
621 }
622 
623 void
624 cvs_server_rdiff(char *data)
625 {
626 	if (chdir(server_currentdir) == -1)
627 		fatal("cvs_server_rdiff: %s", strerror(errno));
628 
629 	cvs_cmdop = CVS_OP_RDIFF;
630 	cmdp->cmd_flags = cvs_cmd_rdiff.cmd_flags;
631 	cvs_diff(server_argc, server_argv);
632 	cvs_server_send_response("ok");
633 }
634 
635 void
636 cvs_server_export(char *data)
637 {
638 	if (chdir(server_currentdir) == -1)
639 		fatal("cvs_server_export: %s", strerror(errno));
640 
641 	cvs_cmdop = CVS_OP_EXPORT;
642 	cmdp->cmd_flags = cvs_cmd_export.cmd_flags;
643 	cvs_export(server_argc, server_argv);
644 	cvs_server_send_response("ok");
645 }
646 
647 void
648 cvs_server_init(char *data)
649 {
650 	if (data == NULL)
651 		fatal("Missing argument for init");
652 
653 	if (current_cvsroot != NULL)
654 		fatal("Root in combination with init is not supported");
655 
656 	if ((current_cvsroot = cvsroot_get(data)) == NULL)
657 		fatal("Invalid argument for init");
658 
659 	cvs_cmdop = CVS_OP_INIT;
660 	cmdp->cmd_flags = cvs_cmd_init.cmd_flags;
661 	cvs_init(server_argc, server_argv);
662 	cvs_server_send_response("ok");
663 }
664 
665 void
666 cvs_server_release(char *data)
667 {
668 	if (chdir(server_currentdir) == -1)
669 		fatal("cvs_server_release: %s", strerror(errno));
670 
671 	cvs_cmdop = CVS_OP_RELEASE;
672 	cmdp->cmd_flags = cvs_cmd_release.cmd_flags;
673 	cvs_release(server_argc, server_argv);
674 	cvs_server_send_response("ok");
675 }
676 
677 void
678 cvs_server_remove(char *data)
679 {
680 	if (chdir(server_currentdir) == -1)
681 		fatal("cvs_server_remove: %s", strerror(errno));
682 
683 	cvs_cmdop = CVS_OP_REMOVE;
684 	cmdp->cmd_flags = cvs_cmd_remove.cmd_flags;
685 	cvs_remove(server_argc, server_argv);
686 	cvs_server_send_response("ok");
687 }
688 
689 void
690 cvs_server_status(char *data)
691 {
692 	if (chdir(server_currentdir) == -1)
693 		fatal("cvs_server_status: %s", strerror(errno));
694 
695 	cvs_cmdop = CVS_OP_STATUS;
696 	cmdp->cmd_flags = cvs_cmd_status.cmd_flags;
697 	cvs_status(server_argc, server_argv);
698 	cvs_server_send_response("ok");
699 }
700 
701 void
702 cvs_server_log(char *data)
703 {
704 	if (chdir(server_currentdir) == -1)
705 		fatal("cvs_server_log: %s", strerror(errno));
706 
707 	cvs_cmdop = CVS_OP_LOG;
708 	cmdp->cmd_flags = cvs_cmd_log.cmd_flags;
709 	cvs_getlog(server_argc, server_argv);
710 	cvs_server_send_response("ok");
711 }
712 
713 void
714 cvs_server_rlog(char *data)
715 {
716 	if (chdir(current_cvsroot->cr_dir) == -1)
717 		fatal("cvs_server_rlog: %s", strerror(errno));
718 
719 	cvs_cmdop = CVS_OP_RLOG;
720 	cmdp->cmd_flags = cvs_cmd_rlog.cmd_flags;
721 	cvs_getlog(server_argc, server_argv);
722 	cvs_server_send_response("ok");
723 }
724 
725 void
726 cvs_server_tag(char *data)
727 {
728 	if (chdir(server_currentdir) == -1)
729 		fatal("cvs_server_tag: %s", strerror(errno));
730 
731 	cvs_cmdop = CVS_OP_TAG;
732 	cmdp->cmd_flags = cvs_cmd_tag.cmd_flags;
733 	cvs_tag(server_argc, server_argv);
734 	cvs_server_send_response("ok");
735 }
736 
737 void
738 cvs_server_rtag(char *data)
739 {
740 	if (chdir(current_cvsroot->cr_dir) == -1)
741 		fatal("cvs_server_rtag: %s", strerror(errno));
742 
743 	cvs_cmdop = CVS_OP_RTAG;
744 	cmdp->cmd_flags = cvs_cmd_rtag.cmd_flags;
745 	cvs_tag(server_argc, server_argv);
746 	cvs_server_send_response("ok");
747 }
748 
749 void
750 cvs_server_update(char *data)
751 {
752 	if (chdir(server_currentdir) == -1)
753 		fatal("cvs_server_update: %s", strerror(errno));
754 
755 	cvs_cmdop = CVS_OP_UPDATE;
756 	cmdp->cmd_flags = cvs_cmd_update.cmd_flags;
757 	cvs_update(server_argc, server_argv);
758 	cvs_server_send_response("ok");
759 }
760 
761 void
762 cvs_server_version(char *data)
763 {
764 	cvs_cmdop = CVS_OP_VERSION;
765 	cmdp->cmd_flags = cvs_cmd_version.cmd_flags;
766 	cvs_version(server_argc, server_argv);
767 	cvs_server_send_response("ok");
768 }
769 
770 void
771 cvs_server_update_entry(const char *resp, struct cvs_file *cf)
772 {
773 	char *p;
774 	char repo[MAXPATHLEN], fpath[MAXPATHLEN];
775 
776 	if ((p = strrchr(cf->file_rpath, ',')) != NULL)
777 		*p = '\0';
778 
779 	cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
780 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", repo, cf->file_name);
781 
782 	cvs_server_send_response("%s %s/", resp, cf->file_wd);
783 	cvs_remote_output(fpath);
784 
785 	if (p != NULL)
786 		*p = ',';
787 }
788 
789 void
790 cvs_server_set_sticky(const char *dir, const char *tag)
791 {
792 	char fpath[MAXPATHLEN];
793 	char repo[MAXPATHLEN];
794 
795 	cvs_get_repository_path(dir, repo, MAXPATHLEN);
796 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/", repo);
797 
798 	cvs_server_send_response("Set-sticky %s/", dir);
799 	cvs_remote_output(fpath);
800 	cvs_remote_output(tag);
801 }
802 
803 void
804 cvs_server_clear_sticky(char *dir)
805 {
806 	char fpath[MAXPATHLEN];
807 	char repo[MAXPATHLEN];
808 
809 	cvs_get_repository_path(dir, repo, MAXPATHLEN);
810 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/", repo);
811 
812 	cvs_server_send_response("Clear-sticky %s//", dir);
813 	cvs_remote_output(fpath);
814 }
815 
816 void
817 cvs_server_exp_modules(char *module)
818 {
819 	struct module_checkout *mo;
820 	struct cvs_filelist *fl;
821 
822 	if (server_argc != 2)
823 		fatal("expand-modules with no arguments");
824 
825 	mo = cvs_module_lookup(server_argv[1]);
826 
827 	RB_FOREACH(fl, cvs_flisthead, &(mo->mc_modules))
828 		cvs_server_send_response("Module-expansion %s", fl->file_path);
829 	cvs_server_send_response("ok");
830 
831 	server_argc--;
832 	xfree(server_argv[1]);
833 	server_argv[1] = NULL;
834 }
835