Annotation of src/usr.bin/cvs/server.c, Revision 1.65
1.65 ! joris 1: /* $OpenBSD: server.c,v 1.64 2007/06/29 12:42:05 xsa Exp $ */
1.1 jfb 2: /*
1.29 joris 3: * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
1.1 jfb 4: *
1.29 joris 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.
1.1 jfb 8: *
1.29 joris 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.
1.1 jfb 16: */
17:
1.55 otto 18: #include <sys/stat.h>
19:
20: #include <errno.h>
21: #include <fcntl.h>
22: #include <libgen.h>
23: #include <stdlib.h>
24: #include <string.h>
25: #include <unistd.h>
1.1 jfb 26:
27: #include "cvs.h"
1.29 joris 28: #include "remote.h"
29:
30: struct cvs_resp cvs_responses[] = {
31: /* this is what our server uses, the client should support it */
32: { "Valid-requests", 1, cvs_client_validreq, RESP_NEEDED },
33: { "ok", 0, cvs_client_ok, RESP_NEEDED},
34: { "error", 0, cvs_client_error, RESP_NEEDED },
35: { "E", 0, cvs_client_e, RESP_NEEDED },
36: { "M", 0, cvs_client_m, RESP_NEEDED },
37: { "Checked-in", 0, cvs_client_checkedin, RESP_NEEDED },
38: { "Updated", 0, cvs_client_updated, RESP_NEEDED },
39: { "Merged", 0, cvs_client_merged, RESP_NEEDED },
40: { "Removed", 0, cvs_client_removed, RESP_NEEDED },
41: { "Remove-entry", 0, cvs_client_remove_entry, RESP_NEEDED },
1.46 xsa 42: { "Set-static-directory", 0, cvs_client_set_static_directory, RESP_NEEDED },
1.45 xsa 43: { "Clear-static-directory", 0, cvs_client_clear_static_directory, RESP_NEEDED },
44: { "Set-sticky", 0, cvs_client_set_sticky, RESP_NEEDED },
45: { "Clear-sticky", 0, cvs_client_clear_sticky, RESP_NEEDED },
1.29 joris 46:
47: /* unsupported responses until told otherwise */
48: { "New-entry", 0, NULL, 0 },
49: { "Created", 0, NULL, 0 },
50: { "Update-existing", 0, NULL, 0 },
51: { "Rcs-diff", 0, NULL, 0 },
52: { "Patched", 0, NULL, 0 },
53: { "Mode", 0, NULL, 0 },
54: { "Mod-time", 0, NULL, 0 },
55: { "Checksum", 0, NULL, 0 },
56: { "Copy-file", 0, NULL, 0 },
57: { "Template", 0, NULL, 0 },
58: { "Set-checkin-prog", 0, NULL, 0 },
59: { "Set-update-prog", 0, NULL, 0 },
60: { "Notified", 0, NULL, 0 },
61: { "Module-expansion", 0, NULL, 0 },
62: { "Wrapper-rcsOption", 0, NULL, 0 },
63: { "Mbinary", 0, NULL, 0 },
64: { "F", 0, NULL, 0 },
65: { "MT", 0, NULL, 0 },
66: { "", -1, NULL, 0 }
67: };
1.1 jfb 68:
1.29 joris 69: int cvs_server(int, char **);
70: char *cvs_server_path = NULL;
1.1 jfb 71:
1.29 joris 72: static char *server_currentdir = NULL;
73: static char *server_argv[CVS_CMD_MAXARG];
74: static int server_argc = 1;
1.8 joris 75:
1.17 jfb 76: struct cvs_cmd cvs_cmd_server = {
1.29 joris 77: CVS_OP_SERVER, 0, "server", { "", "" },
78: "server mode",
1.17 jfb 79: NULL,
1.29 joris 80: NULL,
81: NULL,
82: cvs_server
1.17 jfb 83: };
1.1 jfb 84:
1.14 joris 85:
1.1 jfb 86: int
87: cvs_server(int argc, char **argv)
88: {
1.29 joris 89: char *cmd, *data;
90: struct cvs_req *req;
91:
92: server_argv[0] = xstrdup("server");
93:
1.58 ray 94: (void)xasprintf(&cvs_server_path, "%s/cvs-serv%d", cvs_tmpdir,
95: getpid());
1.29 joris 96:
97: if (mkdir(cvs_server_path, 0700) == -1)
98: fatal("failed to create temporary server directory: %s, %s",
99: cvs_server_path, strerror(errno));
100:
101: if (chdir(cvs_server_path) == -1)
102: fatal("failed to change directory to '%s'", cvs_server_path);
103:
104: for (;;) {
105: cmd = cvs_remote_input();
106:
107: if ((data = strchr(cmd, ' ')) != NULL)
108: (*data++) = '\0';
109:
110: req = cvs_remote_get_request_info(cmd);
111: if (req == NULL)
112: fatal("request '%s' is not supported by our server",
113: cmd);
114:
115: if (req->hdlr == NULL)
116: fatal("opencvs server does not support '%s'", cmd);
1.63 joris 117:
118: if ((req->flags & REQ_NEEDDIR) && (server_currentdir == NULL))
119: fatal("`%s' needs a directory to be sent with "
120: "the `Directory` request first", cmd);
1.29 joris 121:
122: (*req->hdlr)(data);
123: xfree(cmd);
1.14 joris 124: }
125:
1.29 joris 126: return (0);
127: }
128:
129: void
130: cvs_server_send_response(char *fmt, ...)
131: {
132: va_list ap;
1.59 ray 133: char *data;
1.29 joris 134:
135: va_start(ap, fmt);
1.56 ray 136: if (vasprintf(&data, fmt, ap) == -1)
137: fatal("vasprintf: %s", strerror(errno));
1.29 joris 138: va_end(ap);
1.14 joris 139:
1.30 joris 140: cvs_log(LP_TRACE, "%s", data);
1.29 joris 141: cvs_remote_output(data);
142: xfree(data);
143: }
144:
145: void
146: cvs_server_root(char *data)
147: {
148: fatal("duplicate Root request from client, violates the protocol");
149: }
150:
151: void
152: cvs_server_validresp(char *data)
153: {
154: int i;
155: char *sp, *ep;
156: struct cvs_resp *resp;
157:
1.57 ray 158: if ((sp = data) == NULL)
159: fatal("Missing argument for Valid-responses");
160:
1.29 joris 161: do {
162: if ((ep = strchr(sp, ' ')) != NULL)
163: *ep = '\0';
164:
165: resp = cvs_remote_get_response_info(sp);
166: if (resp != NULL)
167: resp->supported = 1;
168:
169: if (ep != NULL)
170: sp = ep + 1;
171: } while (ep != NULL);
172:
173: for (i = 0; cvs_responses[i].supported != -1; i++) {
174: resp = &cvs_responses[i];
175: if ((resp->flags & RESP_NEEDED) &&
176: resp->supported != 1) {
177: fatal("client does not support required '%s'",
178: resp->name);
1.1 jfb 179: }
1.29 joris 180: }
181: }
1.1 jfb 182:
1.29 joris 183: void
184: cvs_server_validreq(char *data)
185: {
186: BUF *bp;
187: char *d;
188: int i, first;
189:
190: first = 0;
191: bp = cvs_buf_alloc(512, BUF_AUTOEXT);
192: for (i = 0; cvs_requests[i].supported != -1; i++) {
193: if (cvs_requests[i].hdlr == NULL)
1.5 jfb 194: continue;
1.1 jfb 195:
1.29 joris 196: if (first != 0)
197: cvs_buf_append(bp, " ", 1);
198: else
199: first++;
200:
201: cvs_buf_append(bp, cvs_requests[i].name,
202: strlen(cvs_requests[i].name));
203: }
204:
205: cvs_buf_putc(bp, '\0');
206: d = cvs_buf_release(bp);
207:
208: cvs_server_send_response("Valid-requests %s", d);
209: cvs_server_send_response("ok");
210: xfree(d);
1.42 xsa 211: }
212:
213: void
1.43 xsa 214: cvs_server_static_directory(char *data)
215: {
216: FILE *fp;
1.51 otto 217: char fpath[MAXPATHLEN];
1.43 xsa 218:
1.54 xsa 219: (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s",
220: server_currentdir, CVS_PATH_STATICENTRIES);
1.43 xsa 221:
222: if ((fp = fopen(fpath, "w+")) == NULL) {
223: cvs_log(LP_ERRNO, "%s", fpath);
1.51 otto 224: return;
1.43 xsa 225: }
226: (void)fclose(fp);
227: }
228:
229: void
1.42 xsa 230: cvs_server_sticky(char *data)
231: {
232: FILE *fp;
1.51 otto 233: char tagpath[MAXPATHLEN];
1.42 xsa 234:
1.57 ray 235: if (data == NULL)
236: fatal("Missing argument for Sticky");
237:
1.54 xsa 238: (void)xsnprintf(tagpath, MAXPATHLEN, "%s/%s",
239: server_currentdir, CVS_PATH_TAG);
1.42 xsa 240:
1.43 xsa 241: if ((fp = fopen(tagpath, "w+")) == NULL) {
1.42 xsa 242: cvs_log(LP_ERRNO, "%s", tagpath);
1.51 otto 243: return;
1.42 xsa 244: }
245:
246: (void)fprintf(fp, "%s\n", data);
247: (void)fclose(fp);
1.29 joris 248: }
249:
250: void
251: cvs_server_globalopt(char *data)
252: {
1.57 ray 253: if (data == NULL)
254: fatal("Missing argument for Global_option");
255:
1.38 xsa 256: if (!strcmp(data, "-l"))
257: cvs_nolog = 1;
1.29 joris 258:
259: if (!strcmp(data, "-n"))
260: cvs_noexec = 1;
1.38 xsa 261:
262: if (!strcmp(data, "-Q"))
263: verbosity = 0;
264:
265: if (!strcmp(data, "-r"))
266: cvs_readonly = 1;
267:
268: if (!strcmp(data, "-t"))
269: cvs_trace = 1;
1.29 joris 270:
271: if (!strcmp(data, "-V"))
272: verbosity = 2;
1.39 xsa 273: }
274:
275: void
276: cvs_server_set(char *data)
277: {
278: char *ep;
279:
1.57 ray 280: if (data == NULL)
281: fatal("Missing argument for Set");
282:
1.39 xsa 283: ep = strchr(data, '=');
284: if (ep == NULL)
285: fatal("no = in variable assignment");
286:
287: *(ep++) = '\0';
288: if (cvs_var_set(data, ep) < 0)
289: fatal("cvs_server_set: cvs_var_set failed");
1.29 joris 290: }
1.1 jfb 291:
1.29 joris 292: void
293: cvs_server_directory(char *data)
294: {
295: CVSENTRIES *entlist;
1.51 otto 296: char *dir, *repo, *parent, entry[CVS_ENT_MAXLINELEN], *dirn, *p;
1.29 joris 297:
298: dir = cvs_remote_input();
1.49 joris 299: STRIP_SLASH(dir);
300:
301: if (strlen(dir) < strlen(current_cvsroot->cr_dir))
1.29 joris 302: fatal("cvs_server_directory: bad Directory request");
303:
1.49 joris 304: repo = dir + strlen(current_cvsroot->cr_dir);
305:
306: /*
307: * This is somewhat required for checkout, as the
308: * directory request will be:
309: *
310: * Directory .
311: * /path/to/cvs/root
312: */
313: if (repo[0] == '\0')
314: p = xstrdup(".");
315: else
316: p = xstrdup(repo + 1);
317:
1.65 ! joris 318: cvs_mkpath(p, NULL);
1.29 joris 319:
1.49 joris 320: if ((dirn = basename(p)) == NULL)
1.29 joris 321: fatal("cvs_server_directory: %s", strerror(errno));
322:
1.49 joris 323: if ((parent = dirname(p)) == NULL)
1.29 joris 324: fatal("cvs_server_directory: %s", strerror(errno));
325:
326: if (strcmp(parent, ".")) {
327: entlist = cvs_ent_open(parent);
1.53 xsa 328: (void)xsnprintf(entry, CVS_ENT_MAXLINELEN, "D/%s////", dirn);
1.29 joris 329:
330: cvs_ent_add(entlist, entry);
331: cvs_ent_close(entlist, ENT_SYNC);
1.1 jfb 332: }
333:
1.29 joris 334: if (server_currentdir != NULL)
335: xfree(server_currentdir);
1.61 ray 336: server_currentdir = p;
1.29 joris 337:
338: xfree(dir);
339: }
340:
341: void
342: cvs_server_entry(char *data)
343: {
344: CVSENTRIES *entlist;
345:
1.57 ray 346: if (data == NULL)
347: fatal("Missing argument for Entry");
348:
1.29 joris 349: entlist = cvs_ent_open(server_currentdir);
350: cvs_ent_add(entlist, data);
351: cvs_ent_close(entlist, ENT_SYNC);
352: }
353:
354: void
355: cvs_server_modified(char *data)
356: {
1.41 xsa 357: int fd;
1.29 joris 358: size_t flen;
359: mode_t fmode;
360: const char *errstr;
1.51 otto 361: char *mode, *len, fpath[MAXPATHLEN];
1.29 joris 362:
1.57 ray 363: if (data == NULL)
364: fatal("Missing argument for Modified");
365:
1.29 joris 366: mode = cvs_remote_input();
367: len = cvs_remote_input();
368:
369: cvs_strtomode(mode, &fmode);
370: xfree(mode);
371:
372: flen = strtonum(len, 0, INT_MAX, &errstr);
373: if (errstr != NULL)
374: fatal("cvs_server_modified: %s", errstr);
375: xfree(len);
376:
1.54 xsa 377: (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", server_currentdir, data);
1.29 joris 378:
379: if ((fd = open(fpath, O_WRONLY | O_CREAT | O_TRUNC)) == -1)
380: fatal("cvs_server_modified: %s: %s", fpath, strerror(errno));
381:
1.48 joris 382: cvs_remote_receive_file(fd, flen);
1.29 joris 383:
384: if (fchmod(fd, 0600) == -1)
385: fatal("cvs_server_modified: failed to set file mode");
386:
387: (void)close(fd);
388: }
389:
390: void
391: cvs_server_useunchanged(char *data)
392: {
393: }
394:
395: void
396: cvs_server_unchanged(char *data)
397: {
1.41 xsa 398: int fd;
1.51 otto 399: char fpath[MAXPATHLEN];
1.29 joris 400: CVSENTRIES *entlist;
401: struct cvs_ent *ent;
402: struct timeval tv[2];
403:
1.57 ray 404: if (data == NULL)
405: fatal("Missing argument for Unchanged");
406:
1.54 xsa 407: (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", server_currentdir, data);
1.29 joris 408:
409: if ((fd = open(fpath, O_RDWR | O_CREAT | O_TRUNC)) == -1)
410: fatal("cvs_server_unchanged: %s: %s", fpath, strerror(errno));
411:
412: entlist = cvs_ent_open(server_currentdir);
413: ent = cvs_ent_get(entlist, data);
414: if (ent == NULL)
415: fatal("received Unchanged request for non-existing file");
416: cvs_ent_close(entlist, ENT_NOSYNC);
417:
1.52 joris 418: tv[0].tv_sec = ent->ce_mtime;
1.29 joris 419: tv[0].tv_usec = 0;
420: tv[1] = tv[0];
421: if (futimes(fd, tv) == -1)
422: fatal("cvs_server_unchanged: failed to set modified time");
423:
424: if (fchmod(fd, 0600) == -1)
425: fatal("cvs_server_unchanged: failed to set mode");
426:
427: cvs_ent_free(ent);
428: (void)close(fd);
429: }
430:
431: void
432: cvs_server_questionable(char *data)
433: {
434: }
435:
436: void
437: cvs_server_argument(char *data)
438: {
1.60 ray 439: if (server_argc >= CVS_CMD_MAXARG)
1.29 joris 440: fatal("cvs_server_argument: too many arguments sent");
1.57 ray 441:
442: if (data == NULL)
443: fatal("Missing argument for Argument");
1.29 joris 444:
445: server_argv[server_argc++] = xstrdup(data);
1.37 xsa 446: }
447:
448: void
449: cvs_server_argumentx(char *data)
450: {
1.44 xsa 451: }
452:
453: void
454: cvs_server_update_patches(char *data)
455: {
456: /*
457: * This does not actually do anything.
458: * It is used to tell that the server is able to
459: * generate patches when given an `update' request.
460: * The client must issue the -u argument to `update'
461: * to receive patches.
462: */
1.29 joris 463: }
464:
465: void
1.31 xsa 466: cvs_server_add(char *data)
467: {
468: if (chdir(server_currentdir) == -1)
469: fatal("cvs_server_add: %s", strerror(errno));
470:
471: cvs_cmdop = CVS_OP_ADD;
472: cvs_add(server_argc, server_argv);
1.50 joris 473: cvs_server_send_response("ok");
474: }
475:
476: void
477: cvs_server_import(char *data)
478: {
479: if (chdir(server_currentdir) == -1)
480: fatal("cvs_server_import: %s", strerror(errno));
481:
482: cvs_cmdop = CVS_OP_IMPORT;
483: cvs_import(server_argc, server_argv);
1.31 xsa 484: cvs_server_send_response("ok");
485: }
1.35 xsa 486:
487: void
488: cvs_server_admin(char *data)
489: {
490: if (chdir(server_currentdir) == -1)
491: fatal("cvs_server_admin: %s", strerror(errno));
492:
493: cvs_cmdop = CVS_OP_ADMIN;
494: cvs_admin(server_argc, server_argv);
495: cvs_server_send_response("ok");
496: }
497:
1.40 xsa 498: void
499: cvs_server_annotate(char *data)
500: {
501: if (chdir(server_currentdir) == -1)
502: fatal("cvs_server_annotate: %s", strerror(errno));
503:
504: cvs_cmdop = CVS_OP_ANNOTATE;
505: cvs_annotate(server_argc, server_argv);
506: cvs_server_send_response("ok");
507: }
1.31 xsa 508:
509: void
1.29 joris 510: cvs_server_commit(char *data)
511: {
512: if (chdir(server_currentdir) == -1)
513: fatal("cvs_server_commit: %s", strerror(errno));
514:
515: cvs_cmdop = CVS_OP_COMMIT;
516: cvs_commit(server_argc, server_argv);
1.49 joris 517: cvs_server_send_response("ok");
518: }
519:
520: void
521: cvs_server_checkout(char *data)
522: { if (chdir(server_currentdir) == -1)
523: fatal("cvs_server_checkout: %s", strerror(errno));
524:
525: cvs_cmdop = CVS_OP_CHECKOUT;
526: cvs_checkout(server_argc, server_argv);
1.29 joris 527: cvs_server_send_response("ok");
528: }
529:
530: void
531: cvs_server_diff(char *data)
532: {
533: if (chdir(server_currentdir) == -1)
534: fatal("cvs_server_diff: %s", strerror(errno));
535:
536: cvs_cmdop = CVS_OP_DIFF;
537: cvs_diff(server_argc, server_argv);
1.34 xsa 538: cvs_server_send_response("ok");
539: }
540:
541: void
542: cvs_server_init(char *data)
543: {
544: if (chdir(server_currentdir) == -1)
545: fatal("cvs_server_init: %s", strerror(errno));
546:
547: cvs_cmdop = CVS_OP_INIT;
548: cvs_init(server_argc, server_argv);
1.64 xsa 549: cvs_server_send_response("ok");
550: }
551:
552: void
553: cvs_server_release(char *data)
554: {
555: if (chdir(server_currentdir) == -1)
556: fatal("cvs_server_init: %s", strerror(errno));
557:
558: cvs_cmdop = CVS_OP_RELEASE;
559: cvs_release(server_argc, server_argv);
1.31 xsa 560: cvs_server_send_response("ok");
561: }
562:
563: void
564: cvs_server_remove(char *data)
565: {
566: if (chdir(server_currentdir) == -1)
567: fatal("cvs_server_remove: %s", strerror(errno));
568:
569: cvs_cmdop = CVS_OP_REMOVE;
570: cvs_remove(server_argc, server_argv);
1.29 joris 571: cvs_server_send_response("ok");
572: }
573:
574: void
575: cvs_server_status(char *data)
576: {
577: if (chdir(server_currentdir) == -1)
578: fatal("cvs_server_status: %s", strerror(errno));
579:
580: cvs_cmdop = CVS_OP_STATUS;
581: cvs_status(server_argc, server_argv);
582: cvs_server_send_response("ok");
583: }
584:
585: void
586: cvs_server_log(char *data)
587: {
588: if (chdir(server_currentdir) == -1)
589: fatal("cvs_server_log: %s", strerror(errno));
590:
591: cvs_cmdop = CVS_OP_LOG;
1.62 niallo 592: cvs_getlog(server_argc, server_argv);
593: cvs_server_send_response("ok");
594: }
595:
596: void
597: cvs_server_rlog(char *data)
598: {
599: char fpath[MAXPATHLEN];
600: struct cvsroot *cvsroot;
601:
602: cvsroot = cvsroot_get(NULL);
603:
604: (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s",
605: cvsroot->cr_dir, server_currentdir);
606:
607: if (chdir(fpath) == -1)
608: fatal("cvs_server_log: %s", strerror(errno));
609:
610: cvs_cmdop = CVS_OP_RLOG;
1.32 xsa 611: cvs_getlog(server_argc, server_argv);
612: cvs_server_send_response("ok");
613: }
614:
615: void
616: cvs_server_tag(char *data)
617: {
618: if (chdir(server_currentdir) == -1)
619: fatal("cvs_server_tag: %s", strerror(errno));
620:
621: cvs_cmdop = CVS_OP_TAG;
1.33 xsa 622: cvs_tag(server_argc, server_argv);
1.29 joris 623: cvs_server_send_response("ok");
624: }
625:
626: void
627: cvs_server_update(char *data)
628: {
629: if (chdir(server_currentdir) == -1)
630: fatal("cvs_server_update: %s", strerror(errno));
1.14 joris 631:
1.29 joris 632: cvs_cmdop = CVS_OP_UPDATE;
633: cvs_update(server_argc, server_argv);
1.36 xsa 634: cvs_server_send_response("ok");
635: }
636:
637: void
638: cvs_server_version(char *data)
639: {
640: cvs_cmdop = CVS_OP_VERSION;
641: cvs_version(server_argc, server_argv);
1.29 joris 642: cvs_server_send_response("ok");
1.47 joris 643: }
644:
645: void
646: cvs_server_update_entry(const char *resp, struct cvs_file *cf)
647: {
1.58 ray 648: char *p;
1.65 ! joris 649: char repo[MAXPATHLEN], fpath[MAXPATHLEN];
1.47 joris 650:
651: if ((p = strrchr(cf->file_rpath, ',')) != NULL)
652: *p = '\0';
653:
1.65 ! joris 654: cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN);
! 655: (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", repo, cf->file_name);
! 656:
1.58 ray 657: cvs_server_send_response("%s %s/", resp, cf->file_wd);
1.65 ! joris 658: cvs_remote_output(fpath);
1.47 joris 659:
660: if (p != NULL)
661: *p = ',';
1.65 ! joris 662: }
! 663:
! 664: void
! 665: cvs_server_set_sticky(char *dir, char *tag)
! 666: {
! 667: char fpath[MAXPATHLEN], tbuf[CVS_ENT_MAXLINELEN];
! 668:
! 669: (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s",
! 670: current_cvsroot->cr_dir, dir);
! 671:
! 672: (void)xsnprintf(tbuf, MAXPATHLEN, "T%s", tag);
! 673:
! 674: cvs_server_send_response("Set-sticky %s", dir);
! 675: cvs_remote_output(fpath);
! 676: cvs_remote_output(tbuf);
! 677: }
! 678:
! 679: void
! 680: cvs_server_clear_sticky(char *dir)
! 681: {
! 682: char fpath[MAXPATHLEN];
! 683:
! 684: (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s",
! 685: current_cvsroot->cr_dir, dir);
! 686:
! 687: cvs_server_send_response("Clear-sticky %s", dir);
! 688: cvs_remote_output(fpath);
1.1 jfb 689: }