xref: /titanic_41/usr/src/cmd/filesync/files.c (revision d3cf9c7d3cb6a89c5ee679d866610bc6baaf2c9a)
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 *
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 *
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 *
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 *
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
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
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 *
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
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
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