xref: /openbsd-src/usr.bin/cvs/util.c (revision 0eea0d082377cb9c3ec583313dc4d52b7b6a4d6d)
1 /*	$OpenBSD: util.c,v 1.7 2004/08/06 20:08:49 jfb Exp $	*/
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 <md5.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <string.h>
37 
38 #include "cvs.h"
39 #include "log.h"
40 #include "file.h"
41 
42 
43 /* letter -> mode type map */
44 static const int cvs_modetypes[26] = {
45 	-1, -1, -1, -1, -1, -1,  1, -1, -1, -1, -1, -1, -1,
46 	-1,  2, -1, -1, -1, -1, -1,  0, -1, -1, -1, -1, -1,
47 };
48 
49 /* letter -> mode map */
50 static const mode_t cvs_modes[3][26] = {
51 	{
52 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
53 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
54 		0,  0,       0,       S_IRUSR, 0,  0,  0,    /* n - u */
55 		0,  S_IWUSR, S_IXUSR, 0,       0             /* v - z */
56 	},
57 	{
58 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
59 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
60 		0,  0,       0,       S_IRGRP, 0,  0,  0,    /* n - u */
61 		0,  S_IWGRP, S_IXGRP, 0,       0             /* v - z */
62 	},
63 	{
64 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
65 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
66 		0,  0,       0,       S_IROTH, 0,  0,  0,    /* n - u */
67 		0,  S_IWOTH, S_IXOTH, 0,       0             /* v - z */
68 	}
69 };
70 
71 
72 /* octal -> string */
73 static const char *cvs_modestr[8] = {
74 	"", "x", "w", "wx", "r", "rx", "rw", "rwx"
75 };
76 
77 
78 
79 
80 /*
81  * cvs_readrepo()
82  *
83  * Read the path stored in the `Repository' CVS file for a given directory
84  * <dir>, and store that path into the buffer pointed to by <dst>, whose size
85  * is <len>.
86  */
87 
88 int
89 cvs_readrepo(const char *dir, char *dst, size_t len)
90 {
91 	size_t dlen;
92 	FILE *fp;
93 	char repo_path[MAXPATHLEN];
94 
95 	snprintf(repo_path, sizeof(repo_path), "%s/CVS/Repository", dir);
96 	fp = fopen(repo_path, "r");
97 	if (fp == NULL) {
98 		return (-1);
99 	}
100 
101 	if (fgets(dst, (int)len, fp) == NULL) {
102 		if (ferror(fp)) {
103 			cvs_log(LP_ERRNO, "failed to read from `%s'",
104 			    repo_path);
105 		}
106 		(void)fclose(fp);
107 		return (-1);
108 	}
109 	dlen = strlen(dst);
110 	if ((dlen > 0) && (dst[dlen - 1] == '\n'))
111 		dst[--dlen] = '\0';
112 
113 	(void)fclose(fp);
114 	return (0);
115 }
116 
117 
118 /*
119  * cvs_strtomode()
120  *
121  * Read the contents of the string <str> and generate a permission mode from
122  * the contents of <str>, which is assumed to have the mode format of CVS.
123  * The CVS protocol specification states that any modes or mode types that are
124  * not recognized should be silently ignored.  This function does not return
125  * an error in such cases, but will issue warnings.
126  * Returns 0 on success, or -1 on failure.
127  */
128 
129 int
130 cvs_strtomode(const char *str, mode_t *mode)
131 {
132 	char type;
133 	mode_t m;
134 	char buf[32], ms[4], *sp, *ep;
135 
136 	m = 0;
137 	strlcpy(buf, str, sizeof(buf));
138 	sp = buf;
139 	ep = sp;
140 
141 	for (sp = buf; ep != NULL; sp = ep + 1) {
142 		ep = strchr(sp, ',');
143 		if (ep != NULL)
144 			*ep = '\0';
145 
146 		if (sscanf(sp, "%c=%3s", &type, ms) != 2) {
147 			cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
148 			continue;
149 		}
150 
151 		if ((type <= 'a') || (type >= 'z') ||
152 		    (cvs_modetypes[type - 'a'] == -1)) {
153 			cvs_log(LP_WARN,
154 			    "invalid mode type `%c'"
155 			    " (`u', `g' or `o' expected), ignoring", type);
156 			continue;
157 		}
158 
159 		/* make type contain the actual mode index */
160 		type = cvs_modetypes[type - 'a'];
161 
162 		for (sp = ms; *sp != '\0'; sp++) {
163 			if ((*sp <= 'a') || (*sp >= 'z') ||
164 			    (cvs_modes[(int)type][*sp - 'a'] == 0)) {
165 				cvs_log(LP_WARN,
166 				    "invalid permission bit `%c'", *sp);
167 			}
168 			else
169 				m |= cvs_modes[(int)type][*sp - 'a'];
170 		}
171 	}
172 
173 	*mode = m;
174 
175 	return (0);
176 }
177 
178 
179 /*
180  * cvs_modetostr()
181  *
182  * Returns 0 on success, or -1 on failure.
183  */
184 
185 int
186 cvs_modetostr(mode_t mode, char *buf, size_t len)
187 {
188 	size_t l;
189 	char tmp[16], *bp;
190 	mode_t um, gm, om;
191 
192 	um = (mode & S_IRWXU) >> 6;
193 	gm = (mode & S_IRWXG) >> 3;
194 	om = mode & S_IRWXO;
195 
196 	bp = buf;
197 	*bp = '\0';
198 	l = 0;
199 
200 	if (um) {
201 		snprintf(tmp, sizeof(tmp), "u=%s", cvs_modestr[um]);
202 		l = strlcat(buf, tmp, len);
203 	}
204 	if (gm) {
205 		if (um)
206 			strlcat(buf, ",", len);
207 		snprintf(tmp, sizeof(tmp), "g=%s", cvs_modestr[gm]);
208 		strlcat(buf, tmp, len);
209 	}
210 	if (om) {
211 		if (um || gm)
212 			strlcat(buf, ",", len);
213 		snprintf(tmp, sizeof(tmp), "o=%s", cvs_modestr[gm]);
214 		strlcat(buf, tmp, len);
215 	}
216 
217 	return (0);
218 }
219 
220 
221 /*
222  * cvs_cksum()
223  *
224  * Calculate the MD5 checksum of the file whose path is <file> and generate
225  * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
226  * given in <len> and must be at least 33.
227  * Returns 0 on success, or -1 on failure.
228  */
229 
230 int
231 cvs_cksum(const char *file, char *dst, size_t len)
232 {
233 	if (len < CVS_CKSUM_LEN) {
234 		cvs_log(LP_WARN, "buffer too small for checksum");
235 		return (-1);
236 	}
237 	if (MD5File(file, dst) == NULL) {
238 		cvs_log(LP_ERRNO, "failed to generate file checksum");
239 		return (-1);
240 	}
241 
242 	return (0);
243 }
244 
245 
246 /*
247  * cvs_splitpath()
248  *
249  * Split a path <path> into the base portion and the filename portion.
250  * The path is copied in <base> and the last delimiter is replaced by a NUL
251  * byte.  The <file> pointer is set to point to the first character after
252  * that delimiter.
253  * Returns 0 on success, or -1 on failure.
254  */
255 
256 int
257 cvs_splitpath(const char *path, char *base, size_t blen, char **file)
258 {
259 	size_t rlen;
260 	char *sp;
261 
262 	if ((rlen = strlcpy(base, path, blen)) >= blen)
263 		return (-1);
264 
265 	while ((rlen > 0) && (base[rlen - 1] == '/'))
266 		base[--rlen] = '\0';
267 
268 	sp = strrchr(base, '/');
269 	if (sp == NULL) {
270 		strlcpy(base, "./", blen);
271 		strlcat(base, path, blen);
272 		sp = base + 1;
273 	}
274 
275 	*sp = '\0';
276 	if (file != NULL)
277 		*file = sp + 1;
278 
279 	return (0);
280 }
281 
282 
283 /*
284  * cvs_getargv()
285  *
286  * Parse a line contained in <line> and generate an argument vector by
287  * splitting the line on spaces and tabs.  The resulting vector is stored in
288  * <argv>, which can accept up to <argvlen> entries.
289  * Returns the number of arguments in the vector, or -1 if an error occured.
290  */
291 
292 int
293 cvs_getargv(const char *line, char **argv, int argvlen)
294 {
295 	u_int i;
296 	int argc, err;
297 	char linebuf[256], qbuf[128], *lp, *cp, *arg;
298 
299 	strlcpy(linebuf, line, sizeof(linebuf));
300 	memset(argv, 0, sizeof(argv));
301 	argc = 0;
302 
303 	/* build the argument vector */
304 	err = 0;
305 	for (lp = linebuf; lp != NULL;) {
306 		if (*lp == '"') {
307 			/* double-quoted string */
308 			lp++;
309 			i = 0;
310 			memset(qbuf, 0, sizeof(qbuf));
311 			while (*lp != '"') {
312 				if (*lp == '\0') {
313 					cvs_log(LP_ERR, "no terminating quote");
314 					err++;
315 					break;
316 				}
317 				else if (*lp == '\\')
318 					lp++;
319 
320 				qbuf[i++] = *lp++;
321 				if (i == sizeof(qbuf)) {
322 					err++;
323 					break;
324 				}
325 			}
326 
327 			arg = qbuf;
328 		}
329 		else {
330 			cp = strsep(&lp, " \t");
331 			if (cp == NULL)
332 				break;
333 			else if (*cp == '\0')
334 				continue;
335 
336 			arg = cp;
337 		}
338 
339 		argv[argc] = strdup(arg);
340 		if (argv[argc] == NULL) {
341 			cvs_log(LP_ERRNO, "failed to copy argument");
342 			err++;
343 			break;
344 		}
345 		argc++;
346 	}
347 
348 	if (err) {
349 		/* ditch the argument vector */
350 		for (i = 0; i < (u_int)argc; i++)
351 			free(argv[i]);
352 		argc = -1;
353 	}
354 
355 	return (argc);
356 }
357 
358 
359 /*
360  * cvs_freeargv()
361  *
362  * Free an argument vector previously generated by cvs_getargv().
363  */
364 
365 void
366 cvs_freeargv(char **argv, int argc)
367 {
368 	int i;
369 
370 	for (i = 0; i < argc; i++)
371 		free(argv[i]);
372 }
373 
374 
375 /*
376  * cvs_mkadmin()
377  *
378  * Create the CVS administrative files within the directory <cdir>.  If the
379  * files already exist, they are kept as is.
380  * Returns 0 on success, or -1 on failure.
381  */
382 
383 int
384 cvs_mkadmin(struct cvs_file *cdir, mode_t mode)
385 {
386 	char path[MAXPATHLEN];
387 	FILE *fp;
388 	CVSENTRIES *ef;
389 	struct stat st;
390 	struct cvsroot *root;
391 
392 	snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, cdir->cf_path);
393 	if ((mkdir(path, mode) == -1) && (errno != EEXIST)) {
394 		cvs_log(LP_ERRNO, "failed to create directory %s", path);
395 		return (-1);
396 	}
397 
398 	/* just create an empty Entries file */
399 	ef = cvs_ent_open(cdir->cf_path, O_WRONLY);
400 	(void)cvs_ent_close(ef);
401 
402 	root = cdir->cf_ddat->cd_root;
403 	snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, cdir->cf_path);
404 	if ((stat(path, &st) != 0) && (errno == ENOENT) && (root != NULL)) {
405 		fp = fopen(path, "w");
406 		if (fp == NULL) {
407 			cvs_log(LP_ERRNO, "failed to open %s", path);
408 			return (-1);
409 		}
410 		if (root->cr_user != NULL) {
411 			fprintf(fp, "%s", root->cr_user);
412 			if (root->cr_pass != NULL)
413 				fprintf(fp, ":%s", root->cr_pass);
414 			if (root->cr_host != NULL)
415 				putc('@', fp);
416 		}
417 
418 		if (root->cr_host != NULL) {
419 			fprintf(fp, "%s", root->cr_host);
420 			if (root->cr_dir != NULL)
421 				putc(':', fp);
422 		}
423 		if (root->cr_dir)
424 			fprintf(fp, "%s", root->cr_dir);
425 		putc('\n', fp);
426 		(void)fclose(fp);
427 	}
428 
429 	snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, cdir->cf_path);
430 	if ((stat(path, &st) != 0) && (errno == ENOENT) &&
431 	    (cdir->cf_ddat->cd_repo != NULL)) {
432 		fp = fopen(path, "w");
433 		if (fp == NULL) {
434 			cvs_log(LP_ERRNO, "failed to open %s", path);
435 			return (-1);
436 		}
437 		fprintf(fp, "%s\n", cdir->cf_ddat->cd_repo);
438 		(void)fclose(fp);
439 	}
440 
441 	return (0);
442 }
443