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