xref: /illumos-gate/usr/src/cmd/filesync/files.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
24  *
25  * module:
26  *	files.c
27  *
28  * purpose:
29  *	routines to examine and manipulate file names
30  *
31  * contents:
32  *	qualify ... ensure that a name is fully qualified
33  *	expand  ... expand env variables within a string or file name
34  *	noblanks .. ensure that a name contains no embdded unescaped blanks
35  *	lex ....... a lexer that can handle escaped/embedded blanks
36  *	wildcards . see whether or not a name contains wild cards
37  *	prefix .... does one string begin with another
38  *	suffix .... does one string end with another
39  *	contains .. does one string contain another
40  *
41  *	cannonize (static) ...	compress redundant "." and ".." out of name
42  *
43  * notes:
44  *	we are interested in embedded blanks because international
45  *	character sets and non-unix file systems can both contain
46  *	the byte 0x20.  Thus, whenever we record a filename in
47  *	file, we must be careful to escape any embedded blanks that
48  *	cause trouble when we re-lex that file later.
49  */
50 #ident	"%W%	%E% SMI"
51 
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <ctype.h>
56 #include <unistd.h>
57 
58 #include "filesync.h"
59 #include "messages.h"
60 
61 static void cannonize(char *name);
62 
63 /*
64  * routine:
65  *	qualify
66  *
67  * purpose:
68  *	to fully qualify a name
69  *
70  * parameters:
71  *	name to be qualified
72  *
73  * returns:
74  *	either original pointer or copy to a new (malloced) buffer
75  *
76  * notes:
77  *	someday I may conclude that I should always make a copy
78  *	so that the caller can know that it is safe to free the parm
79  *
80  *	I thought about this and concluded that there is never a need
81  *	to fully qualify a string containing variables.  If the string
82  *	came from the command line, the variables were already expanded
83  *	and if it came from the rules data base it is required to already
84  *	be fully qualified.
85  */
86 char *
qualify(char * name)87 qualify(char *name)
88 {
89 	char namebuf[ MAX_PATH ];
90 
91 	/* in the simple case, the parameter is already there */
92 	if (*name == '/') {
93 		cannonize(name);
94 		return (name);
95 	}
96 
97 	/* things that begin with variables get the benefit of the doubt */
98 	if (*name == '$') {
99 		cannonize(name);
100 		return (name);
101 	}
102 
103 	/* start with the current working directory	*/
104 	if (getcwd(namebuf, sizeof (namebuf)) == 0) {
105 		fprintf(stderr, gettext(ERR_nocwd), name);
106 		exit(ERR_OTHER);
107 	}
108 
109 	/* make sure we have room for our file name	*/
110 	if ((strlen(namebuf) + strlen(name) + 2) >= sizeof (namebuf)) {
111 		fprintf(stderr, gettext(ERR_longname), name);
112 		exit(ERR_OTHER);
113 	}
114 
115 	/* append the specified file name to it	*/
116 	strcat(namebuf, "/");
117 	strcat(namebuf, name);
118 
119 	/* filter out redundant dots	*/
120 	cannonize(namebuf);
121 
122 	if (opt_debug & DBG_VARS)
123 		fprintf(stderr, "VARS: QUALIFY %s to %s\n", name, namebuf);
124 
125 	/* and return a newly malloc'd copy	*/
126 	return (strdup(namebuf));
127 }
128 
129 /*
130  * routine:
131  *	expand
132  *
133  * purpose:
134  *	to expand variable names within a string
135  *
136  * parameters:
137  *	string to be expanded.  Variable references always begin
138  *	with a $ and are delimited by parens or curleys.
139  *
140  * returns:
141  *	either original pointer or a copy to a new (malloced) buffer
142  *
143  * notes:
144  *	someday I may conclude that I should always make a copy
145  *	so that the caller can know that it is safe to free the parm
146  *
147  *	someday I may decide to support escape conventions for embedding
148  *	$(){} in file names, but I suspec that day will never come.
149  *
150  *	I thought about this and concluded there was no reason to
151  *	fully qualify these names, because the only names that should
152  *	need qualification are src/dst lines from the command line,
153  *	and the shell should have handled those for me.  Once something
154  *	makes it into the database, it is expected to be fully qualified
155  *	already.
156  *
157  *	We are limited to producing strings of length MAX_PATH or less
158  *	and variable names of length MAX_NAME or less.  In practice,
159  *	these limitations should not be a problem.
160  */
161 char *
expand(char * name)162 expand(char *name)
163 {	const char *s;
164 	char *p, *v;
165 	char delim;
166 	char namebuf[ MAX_PATH ];
167 	char varbuf[ MAX_NAME ];
168 
169 	/* first see if there are no variables to be bound */
170 	for (s = name; *s && *s != '$'; s++);
171 	if (*s == 0)
172 		return (name);
173 
174 	/* move through the string, copying and expanding	*/
175 	for (s = name, p = namebuf; *s; s++) {
176 
177 		/* check for overflow	*/
178 		if (p >= &namebuf[ MAX_PATH ]) {
179 			fprintf(stderr, gettext(ERR_longname), name);
180 			exit(ERR_OTHER);
181 		}
182 
183 		/* normal characters, we just copy		*/
184 		if (*s != '$') {
185 			*p++ = *s;
186 			continue;
187 		}
188 
189 		/* figure out how the variable name is delimited */
190 		delim = *++s;
191 		if (delim == '(') {
192 			delim = ')';
193 			s++;
194 		} else if (delim == '{') {
195 			delim = '}';
196 			s++;
197 		} else
198 			delim = 0;
199 
200 		/* copy the variable name up to the closing delimiter */
201 		for (v = varbuf; *s; s++) {
202 			if (isalnum(*s) || (*s == '_') ||
203 				(delim && *s != delim))
204 				*v++ = *s;
205 			else
206 				break;
207 
208 			/* make sure we don't overflow var name buffer	*/
209 			if (v >= &varbuf[MAX_NAME - 1]) {
210 				*v = 0;
211 				fprintf(stderr, gettext(ERR_longname), varbuf);
212 				exit(ERR_OTHER);
213 			}
214 		}
215 
216 		*v = 0;
217 
218 		/* FIX THIS ... there must be a more elegant way */
219 		/* we may have to back up because s will be bumped */
220 		if (delim == 0 || *s != delim)
221 			s--;
222 
223 		/* look up the variable 			*/
224 		v = getenv(varbuf);
225 		if (v == 0 || *v == 0) {
226 			fprintf(stderr, gettext(ERR_undef), varbuf);
227 			return (0);
228 		}
229 
230 		/* copy the variable into the buffer		*/
231 		while (*v)
232 			*p++ = *v++;
233 	}
234 
235 	/* null terminate the copy	*/
236 	*p = 0;
237 
238 	/* compress out any redundant dots and dot-dots	*/
239 	cannonize(namebuf);
240 
241 	if (opt_debug & DBG_VARS)
242 		fprintf(stderr, "VARS: EXPAND %s to %s\n", name, namebuf);
243 
244 	/* and return a newly malloc'd copy	*/
245 	return (strdup(namebuf));
246 }
247 
248 /*
249  * routine:
250  *	noblanks
251  *
252  * purpose:
253  *	to ensure that a name contains no unescaped embedded blanks
254  *
255  * parameters:
256  *	pointer to name
257  *
258  * returns:
259  *	pointer to name or pointer to buffer containing escaped version of name
260  *
261  * notes:
262  *	this routine can be called on full file names, and so can
263  *	conceivably require an arbitrarily large buffer.
264  */
265 const char *
noblanks(const char * name)266 noblanks(const char *name)
267 {
268 	const char *s;
269 	char *p;
270 	static char *namebuf = 0;
271 	static int buflen = 0;
272 	int l;
273 
274 	/* first see if there are no embedded blanks	*/
275 	for (s = name; *s && *s != ' '; s++);
276 	if (*s == 0)
277 		return (name);
278 
279 	/* make sure we have a buffer large enough for the worst case	*/
280 	l = 4 + (2*strlen(name));
281 	for (buflen = MAX_PATH; buflen < l; buflen += MAX_NAME);
282 	namebuf = (char *) realloc(namebuf, buflen);
283 
284 	/* quote the name, and copy it, escaping quotes	*/
285 	p = namebuf;
286 	*p++ = '"';
287 
288 	for (s = name; *s; s++) {
289 		if (*s == '"' || *s == '\\')
290 			*p++ = '\\';
291 		*p++ = *s;
292 	}
293 
294 	*p++ = '"';
295 	*p = 0;
296 
297 	return (namebuf);
298 }
299 
300 /*
301  * routine:
302  *	lex
303  *
304  * purpose:
305  *	my own version of strtok that handles quoting and escaping
306  *
307  * parameters:
308  *	FILE structure for file to read (0 for same string, same file)
309  *
310  * returns:
311  *	pointer to next token
312  *
313  * notes:
314  *	this routine makes no changes to the string it is passed,
315  *	copying tokens into a static buffer.
316  *
317  *	this routine handles continuation lines after reading and
318  *	before the lexing even starts.  This limits continued lines
319  *	to a length of MAX_LINE, but keeps everything else very simple.
320  *	We also, therefore, limit tokens to a maximum length of MAX_LINE.
321  */
322 int lex_linenum;		/* line number in current input file	*/
323 
324 char *
lex(FILE * file)325 lex(FILE *file)
326 {	char c, delim;
327 	char *p;
328 	char *s;
329 	static char *savep;
330 	static char namebuf[ MAX_LINE ];
331 	static char inbuf[ MAX_LINE ];
332 
333 	if (file) {			/* read a new line		*/
334 		p = inbuf + sizeof (inbuf);
335 
336 		/* read the next input line, with all continuations	*/
337 		for (s = inbuf; savep = fgets(s, p - s, file); ) {
338 			lex_linenum++;
339 
340 			/* go find the last character of the input line	*/
341 			while (*s && s[1])
342 				s++;
343 			if (*s == '\n')
344 				s--;
345 
346 			/* see whether or not we need a continuation	*/
347 			if (s < inbuf || *s != '\\')
348 				break;
349 
350 			continue;
351 		}
352 
353 		if (savep == 0)
354 			return (0);
355 
356 		s = inbuf;
357 	} else {			/* continue with old line	*/
358 		if (savep == 0)
359 			return (0);
360 		s = savep;
361 	}
362 	savep = 0;
363 
364 	/* skip over leading white space	*/
365 	while (isspace(*s))
366 		s++;
367 	if (*s == 0)
368 		return (0);
369 
370 	/* see if this is a quoted string	*/
371 	c = *s;
372 	if (c == '\'' || c == '"') {
373 		delim = c;
374 		s++;
375 	} else
376 		delim = 0;
377 
378 	/* copy the token into the buffer	*/
379 	for (p = namebuf; (c = *s) != 0; s++) {
380 		/* literal escape		*/
381 		if (c == '\\') {
382 			s++;
383 			*p++ = *s;
384 			continue;
385 		}
386 
387 		/* closing delimiter		*/
388 		if (c == delim) {
389 			s++;
390 			break;
391 		}
392 
393 		/* delimiting white space	*/
394 		if (delim == 0 && isspace(c))
395 			break;
396 
397 		/* ordinary characters		*/
398 		*p++ = *s;
399 	}
400 
401 
402 	/* remember where we left off		*/
403 	savep = *s ? s : 0;
404 
405 	/* null terminate and return the buffer	*/
406 	*p = 0;
407 	return (namebuf);
408 }
409 
410 /*
411  * routine:
412  *	wildcards
413  *
414  * purpose:
415  *	determine whether or not there are any wild cards in a name
416  *
417  * parameters:
418  *	name to be checked
419  *
420  * returns:
421  *	true/false
422  *
423  * notes:
424  *	we use this to take shortcuts
425  */
426 bool_t
wildcards(const char * name)427 wildcards(const char *name)
428 {	const char *s;
429 	int literal = 0;
430 
431 	for (s = name; *s; s++)
432 		if (literal)
433 			switch (*s) {
434 				case '\'':	/* end of literal string */
435 					literal = 0;
436 					continue;
437 				case '\\':	/* escape next character */
438 					s++;
439 					continue;
440 			}
441 		else
442 			switch (*s) {
443 				case '\'':	/* literal string	*/
444 					literal = 1;
445 					continue;
446 				case '\\':	/* escape next character */
447 					s++;
448 					continue;
449 				case '*':
450 				case '[':
451 				case '{':
452 				case '?':
453 					/* any of these is a wild card	*/
454 					return (TRUE);
455 			}
456 
457 	return (FALSE);
458 }
459 
460 /*
461  * routine:
462  *	cannonize
463  *
464  * purpose:
465  *	to compress redundant dots out of a path
466  *
467  * parameters:
468  *	file name in an editable buffer
469  *
470  * returns:
471  *	void
472  *
473  * notes:
474  *	because we compress the string in place, there is no danger
475  *	of our overflowing any fixed sized buffer.
476  */
477 static void
cannonize(char * name)478 cannonize(char *name)
479 {	char *s, *p;
480 
481 	/* leading dot-slashes	*/
482 	for (s = name; *s == '.' && s[1] == '/'; strcpy(s, &s[2]));
483 
484 	for (s = name; *s; s++) {
485 		/* interesting things happen after slashes	*/
486 		if (*s != '/')
487 			continue;
488 
489 		/* embedded dot-slashes */
490 		while (s[1] == '.' && s[2] == '/')
491 			strcpy(&s[1], &s[3]);
492 
493 		/* embedded slash-dot-dot-slash	*/
494 		if (strncmp(s, "/../", 4) == 0) {
495 			/* scan backwards to eliminate last directory */
496 			for (p = s-1; p > name && *p != '/'; p--);
497 
498 			if (p < name)
499 				p = name;
500 			strcpy(p, &s[3]);
501 		}
502 
503 		continue;
504 	}
505 }
506 
507 /*
508  * routine:
509  *	prefix
510  *
511  * purpose:
512  *	determine whether or not one string begins with another
513  *
514  * parameters:
515  *	string to be tested
516  *	suspected prefix
517  *
518  * returns:
519  *	no	0
520  *	yes	pointer character after prefix
521  */
522 const char *
prefix(const char * s,const char * p)523 prefix(const char *s, const char *p)
524 {
525 	while (*p)
526 		if (*p++ != *s++)
527 			return (0);
528 
529 	return (s);
530 }
531 
532 /*
533  * routine:
534  *	suffix
535  *
536  * purpose:
537  *	determine whether or not one string ends with another
538  *
539  * parameters:
540  *	string to be tested
541  *	suspected suffix
542  *
543  * returns:
544  *	true/false
545  */
546 bool_t
suffix(const char * str,const char * suf)547 suffix(const char *str, const char *suf)
548 {	const char *s;
549 
550 	/* go to where the alleged suffix would start */
551 	for (s = str; *s; s++);
552 	s -= strlen(suf);
553 	if (s < str)
554 		return (FALSE);
555 
556 	/* see if the string ends with the suffix */
557 	while (*suf)
558 		if (*suf++ != *s++)
559 			return (FALSE);
560 
561 	return (TRUE);
562 }
563 
564 /*
565  * routine:
566  *	contains
567  *
568  * purpose:
569  *	determine whether or not one string contains another
570  *
571  * parameters:
572  *	string to be checked
573  *	pattern we are seeking
574  *
575  * returns:
576  *	true/false
577  */
578 bool_t
contains(const char * str,const char * pat)579 contains(const char *str, const char *pat)
580 {	const char *s, *p;
581 
582 	while (*str) {
583 		if (*str++ == *pat) {
584 			for (s = str, p = &pat[1]; *s == *p; s++, p++)
585 				if (p[1] == 0)
586 					return (TRUE);
587 		}
588 	}
589 
590 	return (FALSE);
591 }
592