xref: /openbsd-src/usr.bin/cvs/server.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*	$OpenBSD: server.c,v 1.105 2017/08/28 19:33:20 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/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 		free(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 	free(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 = 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 			buf_putc(bp, ' ');
229 		else
230 			first++;
231 
232 		buf_puts(bp, cvs_requests[i].name);
233 	}
234 
235 	buf_putc(bp, '\0');
236 	d = buf_release(bp);
237 
238 	cvs_server_send_response("Valid-requests %s", d);
239 	cvs_server_send_response("ok");
240 	free(d);
241 }
242 
243 void
244 cvs_server_static_directory(char *data)
245 {
246 	FILE *fp;
247 	char fpath[PATH_MAX];
248 
249 	(void)xsnprintf(fpath, PATH_MAX, "%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[PATH_MAX];
264 
265 	if (data == NULL)
266 		fatal("Missing argument for Sticky");
267 
268 	(void)xsnprintf(tagpath, PATH_MAX, "%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 		free(entry);
367 	}
368 
369 	free(server_currentdir);
370 	server_currentdir = p;
371 
372 	free(dir);
373 }
374 
375 void
376 cvs_server_entry(char *data)
377 {
378 	CVSENTRIES *entlist;
379 
380 	if (data == NULL)
381 		fatal("Missing argument for Entry");
382 
383 	entlist = cvs_ent_open(server_currentdir);
384 	cvs_ent_add(entlist, data);
385 }
386 
387 void
388 cvs_server_modified(char *data)
389 {
390 	int fd;
391 	size_t flen;
392 	mode_t fmode;
393 	const char *errstr;
394 	char *mode, *len, fpath[PATH_MAX];
395 
396 	if (data == NULL)
397 		fatal("Missing argument for Modified");
398 
399 	/* sorry, we have to use TMP_DIR */
400 	disable_fast_checkout = 1;
401 
402 	mode = cvs_remote_input();
403 	len = cvs_remote_input();
404 
405 	cvs_strtomode(mode, &fmode);
406 	free(mode);
407 
408 	flen = strtonum(len, 0, INT_MAX, &errstr);
409 	if (errstr != NULL)
410 		fatal("cvs_server_modified: %s", errstr);
411 	free(len);
412 
413 	(void)xsnprintf(fpath, PATH_MAX, "%s/%s", server_currentdir, data);
414 
415 	if ((fd = open(fpath, O_WRONLY | O_CREAT | O_TRUNC)) == -1)
416 		fatal("cvs_server_modified: %s: %s", fpath, strerror(errno));
417 
418 	cvs_remote_receive_file(fd, flen);
419 
420 	if (fchmod(fd, 0600) == -1)
421 		fatal("cvs_server_modified: failed to set file mode");
422 
423 	(void)close(fd);
424 }
425 
426 void
427 cvs_server_useunchanged(char *data)
428 {
429 }
430 
431 void
432 cvs_server_unchanged(char *data)
433 {
434 	char fpath[PATH_MAX];
435 	CVSENTRIES *entlist;
436 	struct cvs_ent *ent;
437 	char sticky[CVS_ENT_MAXLINELEN];
438 	char rev[CVS_REV_BUFSZ], entry[CVS_ENT_MAXLINELEN];
439 
440 	if (data == NULL)
441 		fatal("Missing argument for Unchanged");
442 
443 	/* sorry, we have to use TMP_DIR */
444 	disable_fast_checkout = 1;
445 
446 	(void)xsnprintf(fpath, PATH_MAX, "%s/%s", server_currentdir, data);
447 
448 	entlist = cvs_ent_open(server_currentdir);
449 	ent = cvs_ent_get(entlist, data);
450 	if (ent == NULL)
451 		fatal("received Unchanged request for non-existing file");
452 
453 	sticky[0] = '\0';
454 	if (ent->ce_tag != NULL)
455 		(void)xsnprintf(sticky, sizeof(sticky), "T%s", ent->ce_tag);
456 
457 	rcsnum_tostr(ent->ce_rev, rev, sizeof(rev));
458 	(void)xsnprintf(entry, sizeof(entry), "/%s/%s/%s/%s/%s",
459 	    ent->ce_name, rev, CVS_SERVER_UNCHANGED, ent->ce_opts ?
460 	    ent->ce_opts : "", sticky);
461 
462 	cvs_ent_free(ent);
463 	cvs_ent_add(entlist, entry);
464 }
465 
466 void
467 cvs_server_questionable(char *data)
468 {
469 	CVSENTRIES *entlist;
470 	char entry[CVS_ENT_MAXLINELEN];
471 
472 	if (data == NULL)
473 		fatal("Questionable request with no data attached");
474 
475 	(void)xsnprintf(entry, sizeof(entry), "/%s/%c///", data,
476 	    CVS_SERVER_QUESTIONABLE);
477 
478 	entlist = cvs_ent_open(server_currentdir);
479 	cvs_ent_add(entlist, entry);
480 
481 	/* sorry, we have to use TMP_DIR */
482 	disable_fast_checkout = 1;
483 }
484 
485 void
486 cvs_server_argument(char *data)
487 {
488 	if (data == NULL)
489 		fatal("Missing argument for Argument");
490 
491 	server_argv = xreallocarray(server_argv, server_argc + 2,
492 	    sizeof(*server_argv));
493 	server_argv[server_argc] = xstrdup(data);
494 	server_argv[++server_argc] = NULL;
495 }
496 
497 void
498 cvs_server_argumentx(char *data)
499 {
500 	int idx;
501 	size_t len;
502 
503 	if (server_argc == 1)
504 		fatal("Protocol Error: ArgumentX without previous argument");
505 
506 	idx = server_argc - 1;
507 
508 	len = strlen(server_argv[idx]) + strlen(data) + 2;
509 	server_argv[idx] = xreallocarray(server_argv[idx], len, sizeof(char));
510 	strlcat(server_argv[idx], "\n", len);
511 	strlcat(server_argv[idx], data, len);
512 }
513 
514 void
515 cvs_server_update_patches(char *data)
516 {
517 	/*
518 	 * This does not actually do anything.
519 	 * It is used to tell that the server is able to
520 	 * generate patches when given an `update' request.
521 	 * The client must issue the -u argument to `update'
522 	 * to receive patches.
523 	 */
524 }
525 
526 void
527 cvs_server_add(char *data)
528 {
529 	if (chdir(server_currentdir) == -1)
530 		fatal("cvs_server_add: %s", strerror(errno));
531 
532 	cvs_cmdop = CVS_OP_ADD;
533 	cmdp->cmd_flags = cvs_cmd_add.cmd_flags;
534 	cvs_add(server_argc, server_argv);
535 	cvs_server_send_response("ok");
536 }
537 
538 void
539 cvs_server_import(char *data)
540 {
541 	if (chdir(server_currentdir) == -1)
542 		fatal("cvs_server_import: %s", strerror(errno));
543 
544 	cvs_cmdop = CVS_OP_IMPORT;
545 	cmdp->cmd_flags = cvs_cmd_import.cmd_flags;
546 	cvs_import(server_argc, server_argv);
547 	cvs_server_send_response("ok");
548 }
549 
550 void
551 cvs_server_admin(char *data)
552 {
553 	if (chdir(server_currentdir) == -1)
554 		fatal("cvs_server_admin: %s", strerror(errno));
555 
556 	cvs_cmdop = CVS_OP_ADMIN;
557 	cmdp->cmd_flags = cvs_cmd_admin.cmd_flags;
558 	cvs_admin(server_argc, server_argv);
559 	cvs_server_send_response("ok");
560 }
561 
562 void
563 cvs_server_annotate(char *data)
564 {
565 	if (chdir(server_currentdir) == -1)
566 		fatal("cvs_server_annotate: %s", strerror(errno));
567 
568 	cvs_cmdop = CVS_OP_ANNOTATE;
569 	cmdp->cmd_flags = cvs_cmd_annotate.cmd_flags;
570 	cvs_annotate(server_argc, server_argv);
571 	cvs_server_send_response("ok");
572 }
573 
574 void
575 cvs_server_rannotate(char *data)
576 {
577 	if (chdir(server_currentdir) == -1)
578 		fatal("cvs_server_rannotate: %s", strerror(errno));
579 
580 	cvs_cmdop = CVS_OP_RANNOTATE;
581 	cmdp->cmd_flags = cvs_cmd_rannotate.cmd_flags;
582 	cvs_annotate(server_argc, server_argv);
583 	cvs_server_send_response("ok");
584 }
585 
586 void
587 cvs_server_commit(char *data)
588 {
589 	if (chdir(server_currentdir) == -1)
590 		fatal("cvs_server_commit: %s", strerror(errno));
591 
592 	cvs_cmdop = CVS_OP_COMMIT;
593 	cmdp->cmd_flags = cvs_cmd_commit.cmd_flags;
594 	cvs_commit(server_argc, server_argv);
595 	cvs_server_send_response("ok");
596 }
597 
598 void
599 cvs_server_checkout(char *data)
600 {
601 	if (chdir(server_currentdir) == -1)
602 		fatal("cvs_server_checkout: %s", strerror(errno));
603 
604 	cvs_cmdop = CVS_OP_CHECKOUT;
605 	cmdp->cmd_flags = cvs_cmd_checkout.cmd_flags;
606 	cvs_checkout(server_argc, server_argv);
607 	cvs_server_send_response("ok");
608 }
609 
610 void
611 cvs_server_diff(char *data)
612 {
613 	if (chdir(server_currentdir) == -1)
614 		fatal("cvs_server_diff: %s", strerror(errno));
615 
616 	cvs_cmdop = CVS_OP_DIFF;
617 	cmdp->cmd_flags = cvs_cmd_diff.cmd_flags;
618 	cvs_diff(server_argc, server_argv);
619 	cvs_server_send_response("ok");
620 }
621 
622 void
623 cvs_server_rdiff(char *data)
624 {
625 	if (chdir(server_currentdir) == -1)
626 		fatal("cvs_server_rdiff: %s", strerror(errno));
627 
628 	cvs_cmdop = CVS_OP_RDIFF;
629 	cmdp->cmd_flags = cvs_cmd_rdiff.cmd_flags;
630 	cvs_diff(server_argc, server_argv);
631 	cvs_server_send_response("ok");
632 }
633 
634 void
635 cvs_server_export(char *data)
636 {
637 	if (chdir(server_currentdir) == -1)
638 		fatal("cvs_server_export: %s", strerror(errno));
639 
640 	cvs_cmdop = CVS_OP_EXPORT;
641 	cmdp->cmd_flags = cvs_cmd_export.cmd_flags;
642 	cvs_export(server_argc, server_argv);
643 	cvs_server_send_response("ok");
644 }
645 
646 void
647 cvs_server_init(char *data)
648 {
649 	if (data == NULL)
650 		fatal("Missing argument for init");
651 
652 	if (current_cvsroot != NULL)
653 		fatal("Root in combination with init is not supported");
654 
655 	if ((current_cvsroot = cvsroot_get(data)) == NULL)
656 		fatal("Invalid argument for init");
657 
658 	cvs_cmdop = CVS_OP_INIT;
659 	cmdp->cmd_flags = cvs_cmd_init.cmd_flags;
660 	cvs_init(server_argc, server_argv);
661 	cvs_server_send_response("ok");
662 }
663 
664 void
665 cvs_server_release(char *data)
666 {
667 	if (chdir(server_currentdir) == -1)
668 		fatal("cvs_server_release: %s", strerror(errno));
669 
670 	cvs_cmdop = CVS_OP_RELEASE;
671 	cmdp->cmd_flags = cvs_cmd_release.cmd_flags;
672 	cvs_release(server_argc, server_argv);
673 	cvs_server_send_response("ok");
674 }
675 
676 void
677 cvs_server_remove(char *data)
678 {
679 	if (chdir(server_currentdir) == -1)
680 		fatal("cvs_server_remove: %s", strerror(errno));
681 
682 	cvs_cmdop = CVS_OP_REMOVE;
683 	cmdp->cmd_flags = cvs_cmd_remove.cmd_flags;
684 	cvs_remove(server_argc, server_argv);
685 	cvs_server_send_response("ok");
686 }
687 
688 void
689 cvs_server_status(char *data)
690 {
691 	if (chdir(server_currentdir) == -1)
692 		fatal("cvs_server_status: %s", strerror(errno));
693 
694 	cvs_cmdop = CVS_OP_STATUS;
695 	cmdp->cmd_flags = cvs_cmd_status.cmd_flags;
696 	cvs_status(server_argc, server_argv);
697 	cvs_server_send_response("ok");
698 }
699 
700 void
701 cvs_server_log(char *data)
702 {
703 	if (chdir(server_currentdir) == -1)
704 		fatal("cvs_server_log: %s", strerror(errno));
705 
706 	cvs_cmdop = CVS_OP_LOG;
707 	cmdp->cmd_flags = cvs_cmd_log.cmd_flags;
708 	cvs_getlog(server_argc, server_argv);
709 	cvs_server_send_response("ok");
710 }
711 
712 void
713 cvs_server_rlog(char *data)
714 {
715 	if (chdir(current_cvsroot->cr_dir) == -1)
716 		fatal("cvs_server_rlog: %s", strerror(errno));
717 
718 	cvs_cmdop = CVS_OP_RLOG;
719 	cmdp->cmd_flags = cvs_cmd_rlog.cmd_flags;
720 	cvs_getlog(server_argc, server_argv);
721 	cvs_server_send_response("ok");
722 }
723 
724 void
725 cvs_server_tag(char *data)
726 {
727 	if (chdir(server_currentdir) == -1)
728 		fatal("cvs_server_tag: %s", strerror(errno));
729 
730 	cvs_cmdop = CVS_OP_TAG;
731 	cmdp->cmd_flags = cvs_cmd_tag.cmd_flags;
732 	cvs_tag(server_argc, server_argv);
733 	cvs_server_send_response("ok");
734 }
735 
736 void
737 cvs_server_rtag(char *data)
738 {
739 	if (chdir(current_cvsroot->cr_dir) == -1)
740 		fatal("cvs_server_rtag: %s", strerror(errno));
741 
742 	cvs_cmdop = CVS_OP_RTAG;
743 	cmdp->cmd_flags = cvs_cmd_rtag.cmd_flags;
744 	cvs_tag(server_argc, server_argv);
745 	cvs_server_send_response("ok");
746 }
747 
748 void
749 cvs_server_update(char *data)
750 {
751 	if (chdir(server_currentdir) == -1)
752 		fatal("cvs_server_update: %s", strerror(errno));
753 
754 	cvs_cmdop = CVS_OP_UPDATE;
755 	cmdp->cmd_flags = cvs_cmd_update.cmd_flags;
756 	cvs_update(server_argc, server_argv);
757 	cvs_server_send_response("ok");
758 }
759 
760 void
761 cvs_server_version(char *data)
762 {
763 	cvs_cmdop = CVS_OP_VERSION;
764 	cmdp->cmd_flags = cvs_cmd_version.cmd_flags;
765 	cvs_version(server_argc, server_argv);
766 	cvs_server_send_response("ok");
767 }
768 
769 void
770 cvs_server_update_entry(const char *resp, struct cvs_file *cf)
771 {
772 	char *p;
773 	char repo[PATH_MAX], fpath[PATH_MAX];
774 
775 	if ((p = strrchr(cf->file_rpath, ',')) != NULL)
776 		*p = '\0';
777 
778 	cvs_get_repository_path(cf->file_wd, repo, PATH_MAX);
779 	(void)xsnprintf(fpath, PATH_MAX, "%s/%s", repo, cf->file_name);
780 
781 	cvs_server_send_response("%s %s/", resp, cf->file_wd);
782 	cvs_remote_output(fpath);
783 
784 	if (p != NULL)
785 		*p = ',';
786 }
787 
788 void
789 cvs_server_set_sticky(const char *dir, char *tag)
790 {
791 	char fpath[PATH_MAX];
792 	char repo[PATH_MAX];
793 
794 	cvs_get_repository_path(dir, repo, PATH_MAX);
795 	(void)xsnprintf(fpath, PATH_MAX, "%s/", repo);
796 
797 	cvs_server_send_response("Set-sticky %s/", dir);
798 	cvs_remote_output(fpath);
799 	cvs_remote_output(tag);
800 }
801 
802 void
803 cvs_server_clear_sticky(char *dir)
804 {
805 	char fpath[PATH_MAX];
806 	char repo[PATH_MAX];
807 
808 	cvs_get_repository_path(dir, repo, PATH_MAX);
809 	(void)xsnprintf(fpath, PATH_MAX, "%s/", repo);
810 
811 	cvs_server_send_response("Clear-sticky %s//", dir);
812 	cvs_remote_output(fpath);
813 }
814 
815 void
816 cvs_server_exp_modules(char *module)
817 {
818 	struct module_checkout *mo;
819 	struct cvs_filelist *fl;
820 
821 	if (server_argc != 2)
822 		fatal("expand-modules with no arguments");
823 
824 	mo = cvs_module_lookup(server_argv[1]);
825 
826 	RB_FOREACH(fl, cvs_flisthead, &(mo->mc_modules))
827 		cvs_server_send_response("Module-expansion %s", fl->file_path);
828 	cvs_server_send_response("ok");
829 
830 	server_argc--;
831 	free(server_argv[1]);
832 	server_argv[1] = NULL;
833 }
834