Annotation of src/usr.bin/cvs/client.c, Revision 1.7
1.1 jfb 1: /* $OpenBSD$ */
2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: *
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
14: *
15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
27: #include <sys/types.h>
28: #include <sys/stat.h>
29:
30: #include <fcntl.h>
31: #include <stdio.h>
32: #include <errno.h>
33: #include <stdlib.h>
34: #include <unistd.h>
35: #include <signal.h>
36: #include <string.h>
37: #include <sysexits.h>
38: #ifdef CVS_ZLIB
39: #include <zlib.h>
40: #endif
41:
42: #include "cvs.h"
43: #include "log.h"
1.7 ! jfb 44: #include "proto.h"
1.1 jfb 45:
46:
47:
48: extern int verbosity;
49: extern int cvs_compress;
50: extern char *cvs_rsh;
51: extern int cvs_trace;
52: extern int cvs_nolog;
53: extern int cvs_readonly;
54:
55:
1.6 jfb 56: static int cvs_client_sendinfo (struct cvsroot *);
1.1 jfb 57: static int cvs_client_initlog (void);
58:
59:
60:
61: static int cvs_server_infd = -1;
62: static int cvs_server_outfd = -1;
63: static FILE *cvs_server_in;
64: static FILE *cvs_server_out;
65:
66: /* protocol log files, if the CVS_CLIENT_LOG environment variable is used */
67: static FILE *cvs_server_inlog;
68: static FILE *cvs_server_outlog;
69:
70: static char cvs_client_buf[4096];
71:
72:
73:
74: /*
75: * cvs_client_connect()
76: *
77: * Open a client connection to the cvs server whose address is given in
1.4 jfb 78: * the <root> variable. The method used to connect depends on the
1.1 jfb 79: * setting of the CVS_RSH variable.
1.4 jfb 80: * Returns 0 on success, or -1 on failure.
1.1 jfb 81: */
82:
83: int
1.4 jfb 84: cvs_client_connect(struct cvsroot *root)
1.1 jfb 85: {
86: int argc, infd[2], outfd[2];
87: pid_t pid;
88: char *argv[16], *cvs_server_cmd;
89:
90: if (pipe(infd) == -1) {
91: cvs_log(LP_ERRNO,
92: "failed to create input pipe for client connection");
93: return (-1);
94: }
95:
96: if (pipe(outfd) == -1) {
97: cvs_log(LP_ERRNO,
98: "failed to create output pipe for client connection");
99: (void)close(infd[0]);
100: (void)close(infd[1]);
101: return (-1);
102: }
103:
104: pid = fork();
105: if (pid == -1) {
106: cvs_log(LP_ERRNO, "failed to fork for cvs server connection");
107: return (-1);
108: }
109: if (pid == 0) {
110: if ((dup2(infd[0], STDIN_FILENO) == -1) ||
111: (dup2(outfd[1], STDOUT_FILENO) == -1)) {
112: cvs_log(LP_ERRNO,
113: "failed to setup standard streams for cvs server");
114: return (-1);
115: }
116: (void)close(infd[1]);
117: (void)close(outfd[0]);
118:
119: argc = 0;
120: argv[argc++] = cvs_rsh;
121:
1.4 jfb 122: if (root->cr_user != NULL) {
1.1 jfb 123: argv[argc++] = "-l";
1.4 jfb 124: argv[argc++] = root->cr_user;
1.1 jfb 125: }
126:
127:
128: cvs_server_cmd = getenv("CVS_SERVER");
129: if (cvs_server_cmd == NULL)
130: cvs_server_cmd = "cvs";
131:
1.4 jfb 132: argv[argc++] = root->cr_host;
1.1 jfb 133: argv[argc++] = cvs_server_cmd;
134: argv[argc++] = "server";
135: argv[argc] = NULL;
136:
137: execvp(argv[0], argv);
138: cvs_log(LP_ERRNO, "failed to exec");
139: exit(EX_OSERR);
140: }
141:
142: /* we are the parent */
143: cvs_server_infd = infd[1];
144: cvs_server_outfd = outfd[0];
145:
146: cvs_server_in = fdopen(cvs_server_infd, "w");
147: if (cvs_server_in == NULL) {
148: cvs_log(LP_ERRNO, "failed to create pipe stream");
149: return (-1);
150: }
151:
152: cvs_server_out = fdopen(cvs_server_outfd, "r");
153: if (cvs_server_out == NULL) {
154: cvs_log(LP_ERRNO, "failed to create pipe stream");
155: return (-1);
156: }
1.4 jfb 157: root->cr_srvin = cvs_server_in;
158: root->cr_srvout = cvs_server_out;
1.1 jfb 159:
160: /* make the streams line-buffered */
161: setvbuf(cvs_server_in, NULL, _IOLBF, 0);
162: setvbuf(cvs_server_out, NULL, _IOLBF, 0);
163:
164: (void)close(infd[0]);
165: (void)close(outfd[1]);
166:
167: cvs_client_initlog();
168:
1.6 jfb 169: cvs_client_sendinfo(root);
1.1 jfb 170:
171: #ifdef CVS_ZLIB
172: /* if compression was requested, initialize it */
173: #endif
174:
175: return (0);
176: }
177:
178:
179: /*
180: * cvs_client_disconnect()
181: *
182: * Disconnect from the cvs server.
183: */
184:
185: void
1.4 jfb 186: cvs_client_disconnect(struct cvsroot *root)
1.1 jfb 187: {
188: cvs_log(LP_DEBUG, "closing client connection");
189: (void)fclose(cvs_server_in);
190: (void)fclose(cvs_server_out);
191: cvs_server_in = NULL;
192: cvs_server_out = NULL;
193: cvs_server_infd = -1;
194: cvs_server_outfd = -1;
195:
196: if (cvs_server_inlog != NULL)
197: fclose(cvs_server_inlog);
198: if (cvs_server_outlog != NULL)
199: fclose(cvs_server_outlog);
200: }
201:
202:
203: /*
204: * cvs_client_sendln()
205: *
206: * Send a single line <line> string to the server. The line is sent as is,
207: * without any modifications.
208: * Returns 0 on success, or -1 on failure.
209: */
210:
211: int
212: cvs_client_sendln(const char *line)
213: {
214: int nl;
215: size_t len;
216:
217: nl = 0;
218: len = strlen(line);
219:
220: if ((len > 0) && (line[len - 1] != '\n'))
221: nl = 1;
222:
223: if (cvs_server_inlog != NULL) {
224: fputs(line, cvs_server_inlog);
225: if (nl)
226: fputc('\n', cvs_server_inlog);
227: }
228: fputs(line, cvs_server_in);
229: if (nl)
230: fputc('\n', cvs_server_in);
231:
232: return (0);
233: }
234:
235:
236: /*
237: * cvs_client_sendraw()
238: *
239: * Send the first <len> bytes from the buffer <src> to the server.
240: */
241:
242: int
243: cvs_client_sendraw(const void *src, size_t len)
244: {
245: if (cvs_server_inlog != NULL)
246: fwrite(src, sizeof(char), len, cvs_server_inlog);
247: if (fwrite(src, sizeof(char), len, cvs_server_in) < len) {
248: return (-1);
249: }
250:
251: return (0);
252: }
253:
254:
255: /*
256: * cvs_client_recvraw()
257: *
258: * Receive the first <len> bytes from the buffer <src> to the server.
259: */
260:
261: ssize_t
262: cvs_client_recvraw(void *dst, size_t len)
263: {
264: size_t ret;
265:
266: ret = fread(dst, sizeof(char), len, cvs_server_out);
267: if (ret == 0)
268: return (-1);
269: if (cvs_server_outlog != NULL)
270: fwrite(dst, sizeof(char), len, cvs_server_outlog);
271: return (ssize_t)ret;
272: }
273:
274:
275: /*
276: * cvs_client_getln()
277: *
278: * Get a line from the server's output and store it in <lbuf>. The terminating
279: * newline character is stripped from the result.
280: */
281:
282: int
283: cvs_client_getln(char *lbuf, size_t len)
284: {
285: size_t rlen;
286:
287: if (fgets(lbuf, len, cvs_server_out) == NULL) {
288: if (ferror(cvs_server_out)) {
289: cvs_log(LP_ERRNO, "failed to read line from server");
290: return (-1);
291: }
292:
293: if (feof(cvs_server_out))
294: *lbuf = '\0';
295: }
296:
297: if (cvs_server_outlog != NULL)
298: fputs(lbuf, cvs_server_outlog);
299:
300: rlen = strlen(lbuf);
301: if ((rlen > 0) && (lbuf[rlen - 1] == '\n'))
302: lbuf[--rlen] = '\0';
303:
304: return (0);
305: }
306:
307:
308: /*
309: * cvs_client_sendinfo()
310: *
311: * Initialize the connection status by first requesting the list of
312: * supported requests from the server. Then, we send the CVSROOT variable
313: * with the `Root' request.
314: * Returns 0 on success, or -1 on failure.
315: */
316:
317: static int
1.6 jfb 318: cvs_client_sendinfo(struct cvsroot *root)
1.1 jfb 319: {
320: char *vresp;
321: /*
322: * First, send the server the list of valid responses, then ask
323: * for valid requests
324: */
325:
326: vresp = cvs_resp_getvalid();
327: if (vresp == NULL) {
328: cvs_log(LP_ERR, "can't generate list of valid responses");
329: return (-1);
330: }
331:
332: if (cvs_client_sendreq(CVS_REQ_VALIDRESP, vresp, 0) < 0) {
333: }
334: free(vresp);
335:
336: if (cvs_client_sendreq(CVS_REQ_VALIDREQ, NULL, 1) < 0) {
337: cvs_log(LP_ERR, "failed to get valid requests from server");
338: return (-1);
339: }
340:
1.6 jfb 341: /* not sure why, but we have to send this */
342: if (cvs_client_sendreq(CVS_REQ_USEUNCHANGED, NULL, 0) < 0)
343: return (-1);
344:
1.1 jfb 345: /* now share our global options with the server */
346: if (verbosity == 1)
347: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-q", 0);
348: else if (verbosity == 0)
349: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-Q", 0);
350:
351: if (cvs_nolog)
352: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-l", 0);
353: if (cvs_readonly)
354: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-r", 0);
355: if (cvs_trace)
356: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-t", 0);
357:
358: /* now send the CVSROOT to the server */
1.6 jfb 359: if (cvs_client_sendreq(CVS_REQ_ROOT, root->cr_dir, 0) < 0)
1.1 jfb 360: return (-1);
361:
362: return (0);
363: }
364:
365:
366: /*
367: * cvs_client_senddir()
368: *
369: * Send a `Directory' request along with the 2 paths that follow it.
370: */
371:
372: int
373: cvs_client_senddir(const char *dir)
374: {
375: char repo[MAXPATHLEN], buf[MAXPATHLEN];
376:
1.2 jfb 377: if (cvs_readrepo(dir, repo, sizeof(repo)) < 0) {
378: repo[0] = '\0';
379: strlcpy(buf, cvs_root->cr_dir, sizeof(buf));
380: }
381: else {
382: snprintf(buf, sizeof(buf), "%s/%s", cvs_root->cr_dir, repo);
383: }
1.1 jfb 384:
385: if ((cvs_client_sendreq(CVS_REQ_DIRECTORY, dir, 0) < 0) ||
386: (cvs_client_sendln(buf) < 0))
387: return (-1);
388:
389: return (0);
390: }
391:
392:
393: /*
394: * cvs_client_sendarg()
395: *
396: * Send the argument <arg> to the server. The argument <append> is used to
397: * determine if the argument should be simply appended to the last argument
398: * sent or if it should be created as a new argument (0).
399: */
400:
401: int
402: cvs_client_sendarg(const char *arg, int append)
403: {
404: return cvs_client_sendreq(((append == 0) ?
405: CVS_REQ_ARGUMENT : CVS_REQ_ARGUMENTX), arg, 0);
406: }
407:
408:
409: /*
410: * cvs_client_sendentry()
411: *
412: * Send an `Entry' request to the server along with the mandatory fields from
413: * the CVS entry <ent> (which are the name and revision).
414: */
415:
416: int
417: cvs_client_sendentry(const struct cvs_ent *ent)
418: {
419: char ebuf[128], numbuf[64];
420:
421: snprintf(ebuf, sizeof(ebuf), "/%s/%s///", ent->ce_name,
422: rcsnum_tostr(ent->ce_rev, numbuf, sizeof(numbuf)));
423:
424: return cvs_client_sendreq(CVS_REQ_ENTRY, ebuf, 0);
425: }
426:
427:
428: /*
429: * cvs_client_initlog()
430: *
431: * Initialize protocol logging if the CVS_CLIENT_LOG environment variable is
432: * set. In this case, the variable's value is used as a path to which the
433: * appropriate suffix is added (".in" for server input and ".out" for server
434: * output.
435: * Returns 0 on success, or -1 on failure.
436: */
437:
438: static int
439: cvs_client_initlog(void)
440: {
441: char *env, fpath[MAXPATHLEN];
442:
443: env = getenv("CVS_CLIENT_LOG");
444: if (env == NULL)
445: return (0);
446:
447: strlcpy(fpath, env, sizeof(fpath));
448: strlcat(fpath, ".in", sizeof(fpath));
449: cvs_server_inlog = fopen(fpath, "w");
450: if (cvs_server_inlog == NULL) {
451: cvs_log(LP_ERRNO, "failed to open server input log `%s'",
452: fpath);
453: return (-1);
454: }
455:
456: strlcpy(fpath, env, sizeof(fpath));
457: strlcat(fpath, ".out", sizeof(fpath));
458: cvs_server_outlog = fopen(fpath, "w");
459: if (cvs_server_outlog == NULL) {
460: cvs_log(LP_ERRNO, "failed to open server output log `%s'",
461: fpath);
462: return (-1);
463: }
464:
465: /* make the streams line-buffered */
466: setvbuf(cvs_server_inlog, NULL, _IOLBF, 0);
467: setvbuf(cvs_server_outlog, NULL, _IOLBF, 0);
468:
469: return (0);
470: }