xref: /illumos-gate/usr/src/cmd/make/bin/files.cc (revision 10d63b7db37a83b39c7f511cf9426c9d03ea0760)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  *	files.c
28  *
29  *	Various file related routines:
30  *		Figure out if file exists
31  *		Wildcard resolution for directory reader
32  *		Directory reader
33  */
34 
35 
36 /*
37  * Included files
38  */
39 #include <dirent.h>		/* opendir() */
40 #include <errno.h>		/* errno */
41 #include <mk/defs.h>
42 #include <mksh/macro.h>		/* getvar() */
43 #include <mksh/misc.h>		/* get_prop(), append_prop() */
44 #include <sys/stat.h>		/* lstat() */
45 #include <libintl.h>
46 
47 /*
48  * Defined macros
49  */
50 
51 /*
52  * typedefs & structs
53  */
54 
55 /*
56  * Static variables
57  */
58 
59 /*
60  * File table of contents
61  */
62 extern	timestruc_t&	exists(register Name target);
63 extern  void		set_target_stat(register Name target, struct stat buf);
64 static	timestruc_t&	vpath_exists(register Name target);
65 static	Name		enter_file_name(wchar_t *name_string, wchar_t *library);
66 static	Boolean		star_match(register char *string, register char *pattern);
67 static	Boolean		amatch(register wchar_t *string, register wchar_t *pattern);
68 
69 /*
70  *	exists(target)
71  *
72  *	Figure out the timestamp for one target.
73  *
74  *	Return value:
75  *				The time the target was created
76  *
77  *	Parameters:
78  *		target		The target to check
79  *
80  *	Global variables used:
81  *		debug_level	Should we trace the stat call?
82  *		recursion_level	Used for tracing
83  *		vpath_defined	Was the variable VPATH defined in environment?
84  */
85 timestruc_t&
86 exists(register Name target)
87 {
88 	struct stat		buf;
89 	register int		result;
90 
91 	/* We cache stat information. */
92 	if (target->stat.time != file_no_time) {
93 		return target->stat.time;
94 	}
95 
96 	/*
97 	 * If the target is a member, we have to extract the time
98 	 * from the archive.
99 	 */
100 	if (target->is_member &&
101 	    (get_prop(target->prop, member_prop) != NULL)) {
102 		return read_archive(target);
103 	}
104 
105 	if (debug_level > 1) {
106 		(void) printf("%*sstat(%s)\n",
107 		              recursion_level,
108 		              "",
109 		              target->string_mb);
110 	}
111 
112 	result = lstat_vroot(target->string_mb, &buf, NULL, VROOT_DEFAULT);
113 	if ((result != -1) && ((buf.st_mode & S_IFMT) == S_IFLNK)) {
114                 /*
115 		 * If the file is a symbolic link, we remember that
116 		 * and then we get the status for the refd file.
117 		 */
118                 target->stat.is_sym_link = true;
119                 result = stat_vroot(target->string_mb, &buf, NULL, VROOT_DEFAULT);
120         } else {
121                 target->stat.is_sym_link = false;
122 	}
123 
124 	if (result < 0) {
125 		target->stat.time = file_doesnt_exist;
126 		target->stat.stat_errno = errno;
127 		if ((errno == ENOENT) &&
128 		    vpath_defined &&
129 /* azv, fixing bug 1262942, VPATH works with a leaf name
130  * but not a directory name.
131  */
132 		    (target->string_mb[0] != (int) slash_char) ) {
133 /* BID_1214655 */
134 /* azv */
135 			vpath_exists(target);
136 			// return vpath_exists(target);
137 		}
138 	} else {
139 		/* Save all the information we need about the file */
140 		target->stat.stat_errno = 0;
141 		target->stat.is_file = true;
142 		target->stat.mode = buf.st_mode & 0777;
143 		target->stat.size = buf.st_size;
144 		target->stat.is_dir =
145 		  BOOLEAN((buf.st_mode & S_IFMT) == S_IFDIR);
146 		if (target->stat.is_dir) {
147 			target->stat.time = file_is_dir;
148 		} else {
149 			/* target->stat.time = buf.st_mtime; */
150 /* BID_1129806 */
151 /* vis@nbsp.nsk.su */
152 			target->stat.time = MAX(buf.st_mtim, file_min_time);
153 		}
154 	}
155 	if ((target->colon_splits > 0) &&
156 	    (get_prop(target->prop, time_prop) == NULL)) {
157 		append_prop(target, time_prop)->body.time.time =
158 		  target->stat.time;
159 	}
160 	return target->stat.time;
161 }
162 
163 /*
164  *	set_target_stat( target, buf)
165  *
166  *	Called by exists() to set some stat fields in the Name structure
167  *	to those read by the stat_vroot() call (from disk).
168  *
169  *	Parameters:
170  *		target		The target whose stat field is set
171  *		buf		stat values (on disk) of the file
172  *				represented by target.
173  */
174 void
175 set_target_stat(register Name target, struct stat buf)
176 {
177 	target->stat.stat_errno = 0;
178 	target->stat.is_file = true;
179 	target->stat.mode = buf.st_mode & 0777;
180 	target->stat.size = buf.st_size;
181 	target->stat.is_dir =
182 	  BOOLEAN((buf.st_mode & S_IFMT) == S_IFDIR);
183 	if (target->stat.is_dir) {
184 		target->stat.time = file_is_dir;
185 	} else {
186 		/* target->stat.time = buf.st_mtime; */
187 /* BID_1129806 */
188 /* vis@nbsp.nsk.su */
189 		target->stat.time = MAX(buf.st_mtim, file_min_time);
190 	}
191 }
192 
193 
194 /*
195  *	vpath_exists(target)
196  *
197  *	Called if exists() discovers that there is a VPATH defined.
198  *	This function stats the VPATH translation of the target.
199  *
200  *	Return value:
201  *				The time the target was created
202  *
203  *	Parameters:
204  *		target		The target to check
205  *
206  *	Global variables used:
207  *		vpath_name	The Name "VPATH", used to get macro value
208  */
209 static timestruc_t&
210 vpath_exists(register Name target)
211 {
212 	wchar_t			*vpath;
213 	wchar_t			file_name[MAXPATHLEN];
214 	wchar_t			*name_p;
215 	Name			alias;
216 
217 	/*
218 	 * To avoid recursive search through VPATH when exists(alias) is called
219 	 */
220 	vpath_defined = false;
221 
222 	Wstring wcb(getvar(vpath_name));
223 	Wstring wcb1(target);
224 
225 	vpath = wcb.get_string();
226 
227 	while (*vpath != (int) nul_char) {
228 		name_p = file_name;
229 		while ((*vpath != (int) colon_char) &&
230 		       (*vpath != (int) nul_char)) {
231 			*name_p++ = *vpath++;
232 		}
233 		*name_p++ = (int) slash_char;
234 		(void) wcscpy(name_p, wcb1.get_string());
235 		alias = GETNAME(file_name, FIND_LENGTH);
236 		if (exists(alias) != file_doesnt_exist) {
237 			target->stat.is_file = true;
238 			target->stat.mode = alias->stat.mode;
239 			target->stat.size = alias->stat.size;
240 			target->stat.is_dir = alias->stat.is_dir;
241 			target->stat.time = alias->stat.time;
242 			maybe_append_prop(target, vpath_alias_prop)->
243 						body.vpath_alias.alias = alias;
244 			target->has_vpath_alias_prop = true;
245 			vpath_defined = true;
246 			return alias->stat.time;
247 		}
248 		while ((*vpath != (int) nul_char) &&
249 		       ((*vpath == (int) colon_char) || iswspace(*vpath))) {
250 			vpath++;
251 		}
252 	}
253 	/*
254 	 * Restore vpath_defined
255 	 */
256 	vpath_defined = true;
257 	return target->stat.time;
258 }
259 
260 /*
261  *	read_dir(dir, pattern, line, library)
262  *
263  *	Used to enter the contents of directories into makes namespace.
264  *	Presence of a file is important when scanning for implicit rules.
265  *	read_dir() is also used to expand wildcards in dependency lists.
266  *
267  *	Return value:
268  *				Non-0 if we found files to match the pattern
269  *
270  *	Parameters:
271  *		dir		Path to the directory to read
272  *		pattern		Pattern for that files should match or NULL
273  *		line		When we scan using a pattern we enter files
274  *				we find as dependencies for this line
275  *		library		If we scan for "lib.a(<wildcard-member>)"
276  *
277  *	Global variables used:
278  *		debug_level	Should we trace the dir reading?
279  *		dot		The Name ".", compared against
280  *		sccs_dir_path	The path to the SCCS dir (from PROJECTDIR)
281  *		vpath_defined	Was the variable VPATH defined in environment?
282  *		vpath_name	The Name "VPATH", use to get macro value
283  */
284 int
285 read_dir(Name dir, wchar_t *pattern, Property line, wchar_t *library)
286 {
287 	wchar_t			file_name[MAXPATHLEN];
288 	wchar_t			*file_name_p = file_name;
289 	Name			file;
290 	wchar_t			plain_file_name[MAXPATHLEN];
291 	wchar_t			*plain_file_name_p;
292 	Name			plain_file;
293 	wchar_t			tmp_wcs_buffer[MAXPATHLEN];
294 	DIR			*dir_fd;
295 	int			m_local_dependency=0;
296 #define d_fileno d_ino
297         register struct dirent  *dp;
298 	wchar_t			*vpath = NULL;
299 	wchar_t			*p;
300 	int			result = 0;
301 
302 	if(dir->hash.length >= MAXPATHLEN) {
303 		return 0;
304 	}
305 
306 	Wstring wcb(dir);
307 	Wstring vps;
308 
309 	/* A directory is only read once unless we need to expand wildcards. */
310 	if (pattern == NULL) {
311 		if (dir->has_read_dir) {
312 			return 0;
313 		}
314 		dir->has_read_dir = true;
315 	}
316 	/* Check if VPATH is active and setup list if it is. */
317 	if (vpath_defined && (dir == dot)) {
318 		vps.init(getvar(vpath_name));
319 		vpath = vps.get_string();
320 	}
321 
322 	/*
323 	 * Prepare the string where we build the full name of the
324 	 * files in the directory.
325 	 */
326 	if ((dir->hash.length > 1) || (wcb.get_string()[0] != (int) period_char)) {
327 		(void) wcscpy(file_name, wcb.get_string());
328 		MBSTOWCS(wcs_buffer, "/");
329 		(void) wcscat(file_name, wcs_buffer);
330 		file_name_p = file_name + wcslen(file_name);
331 	}
332 
333 	/* Open the directory. */
334 vpath_loop:
335 	dir_fd = opendir(dir->string_mb);
336 	if (dir_fd == NULL) {
337 		return 0;
338 	}
339 
340 	/* Read all the directory entries. */
341 	while ((dp = readdir(dir_fd)) != NULL) {
342 		/* We ignore "." and ".." */
343 		if ((dp->d_fileno == 0) ||
344 		    ((dp->d_name[0] == (int) period_char) &&
345 		     ((dp->d_name[1] == 0) ||
346 		      ((dp->d_name[1] == (int) period_char) &&
347 		       (dp->d_name[2] == 0))))) {
348 			continue;
349 		}
350 		/*
351 		 * Build the full name of the file using whatever
352 		 * path supplied to the function.
353 		 */
354 		MBSTOWCS(tmp_wcs_buffer, dp->d_name);
355 		(void) wcscpy(file_name_p, tmp_wcs_buffer);
356 		file = enter_file_name(file_name, library);
357 		if ((pattern != NULL) && amatch(tmp_wcs_buffer, pattern)) {
358 			/*
359 			 * If we are expanding a wildcard pattern, we
360 			 * enter the file as a dependency for the target.
361 			 */
362 			if (debug_level > 0){
363 				WCSTOMBS(mbs_buffer, pattern);
364 				(void) printf(gettext("'%s: %s' due to %s expansion\n"),
365 					      line->body.line.target->string_mb,
366 					      file->string_mb,
367 					      mbs_buffer);
368 			}
369 			enter_dependency(line, file, false);
370 			result++;
371 		} else {
372 			/*
373 			 * If the file has an SCCS/s. file,
374 			 * we will detect that later on.
375 			 */
376 			file->stat.has_sccs = NO_SCCS;
377 		/*
378 		 * If this is an s. file, we also enter it as if it
379 		 * existed in the plain directory.
380 		 */
381 		if ((dp->d_name[0] == 's') &&
382 		    (dp->d_name[1] == (int) period_char)) {
383 
384 			MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
385 			plain_file_name_p = plain_file_name;
386 			(void) wcscpy(plain_file_name_p, tmp_wcs_buffer);
387 			plain_file = GETNAME(plain_file_name, FIND_LENGTH);
388 			plain_file->stat.is_file = true;
389 			plain_file->stat.has_sccs = HAS_SCCS;
390 			/*
391 			 * Enter the s. file as a dependency for the
392 			 * plain file.
393 			 */
394 			maybe_append_prop(plain_file, sccs_prop)->
395 			  body.sccs.file = file;
396 			MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
397 			if ((pattern != NULL) &&
398 			    amatch(tmp_wcs_buffer, pattern)) {
399 				if (debug_level > 0) {
400 					WCSTOMBS(mbs_buffer, pattern);
401 					(void) printf(gettext("'%s: %s' due to %s expansion\n"),
402 						      line->body.line.target->
403 						      string_mb,
404 						      plain_file->string_mb,
405 						      mbs_buffer);
406 				}
407 				enter_dependency(line, plain_file, false);
408 				result++;
409 			}
410 		}
411 	      }
412 	}
413 	(void) closedir(dir_fd);
414 	if ((vpath != NULL) && (*vpath != (int) nul_char)) {
415 		while ((*vpath != (int) nul_char) &&
416 		       (iswspace(*vpath) || (*vpath == (int) colon_char))) {
417 			vpath++;
418 		}
419 		p = vpath;
420 		while ((*vpath != (int) colon_char) &&
421 		       (*vpath != (int) nul_char)) {
422 			vpath++;
423 		}
424 		if (vpath > p) {
425 			dir = GETNAME(p, vpath - p);
426 			goto vpath_loop;
427 		}
428 	}
429 /*
430  * look into SCCS directory only if it's not svr4. For svr4 dont do that.
431  */
432 
433 /*
434  * Now read the SCCS directory.
435  * Files in the SCSC directory are considered to be part of the set of
436  * files in the plain directory. They are also entered in their own right.
437  * Prepare the string where we build the true name of the SCCS files.
438  */
439 	(void) wcsncpy(plain_file_name,
440 		      file_name,
441 		      file_name_p - file_name);
442 	plain_file_name[file_name_p - file_name] = 0;
443 	plain_file_name_p = plain_file_name + wcslen(plain_file_name);
444 
445         if(!svr4) {
446 
447 	  if (sccs_dir_path != NULL) {
448 		wchar_t		tmp_wchar;
449 		wchar_t		path[MAXPATHLEN];
450 		char		mb_path[MAXPATHLEN];
451 
452 		if (file_name_p - file_name > 0) {
453 			tmp_wchar = *file_name_p;
454 			*file_name_p = 0;
455 			WCSTOMBS(mbs_buffer, file_name);
456 			(void) sprintf(mb_path, "%s/%s/SCCS",
457 				        sccs_dir_path,
458 				        mbs_buffer);
459 			*file_name_p = tmp_wchar;
460 		} else {
461 			(void) sprintf(mb_path, "%s/SCCS", sccs_dir_path);
462 		}
463 		MBSTOWCS(path, mb_path);
464 		(void) wcscpy(file_name, path);
465 	  } else {
466 		MBSTOWCS(wcs_buffer, "SCCS");
467 		(void) wcscpy(file_name_p, wcs_buffer);
468 	  }
469 	} else {
470 		MBSTOWCS(wcs_buffer, ".");
471 		(void) wcscpy(file_name_p, wcs_buffer);
472 	}
473 	/* Internalize the constructed SCCS dir name. */
474 	(void) exists(dir = GETNAME(file_name, FIND_LENGTH));
475 	/* Just give up if the directory file doesnt exist. */
476 	if (!dir->stat.is_file) {
477 		return result;
478 	}
479 	/* Open the directory. */
480 	dir_fd = opendir(dir->string_mb);
481 	if (dir_fd == NULL) {
482 		return result;
483 	}
484 	MBSTOWCS(wcs_buffer, "/");
485 	(void) wcscat(file_name, wcs_buffer);
486 	file_name_p = file_name + wcslen(file_name);
487 
488 	while ((dp = readdir(dir_fd)) != NULL) {
489 		if ((dp->d_fileno == 0) ||
490 		    ((dp->d_name[0] == (int) period_char) &&
491 		     ((dp->d_name[1] == 0) ||
492 		      ((dp->d_name[1] == (int) period_char) &&
493 		       (dp->d_name[2] == 0))))) {
494 			continue;
495 		}
496 		/* Construct and internalize the true name of the SCCS file. */
497 		MBSTOWCS(wcs_buffer, dp->d_name);
498 		(void) wcscpy(file_name_p, wcs_buffer);
499 		file = GETNAME(file_name, FIND_LENGTH);
500 		file->stat.is_file = true;
501 		file->stat.has_sccs = NO_SCCS;
502 		/*
503 		 * If this is an s. file, we also enter it as if it
504 		 * existed in the plain directory.
505 		 */
506 		if ((dp->d_name[0] == 's') &&
507 		    (dp->d_name[1] == (int) period_char)) {
508 
509 			MBSTOWCS(wcs_buffer, dp->d_name + 2);
510 			(void) wcscpy(plain_file_name_p, wcs_buffer);
511 			plain_file = GETNAME(plain_file_name, FIND_LENGTH);
512 			plain_file->stat.is_file = true;
513 			plain_file->stat.has_sccs = HAS_SCCS;
514 				/* if sccs dependency is already set,skip */
515 			if(plain_file->prop) {
516 				Property sprop = get_prop(plain_file->prop,sccs_prop);
517 				if(sprop != NULL) {
518 					if (sprop->body.sccs.file) {
519 						goto try_pattern;
520 					}
521 				}
522 			}
523 
524 			/*
525 			 * Enter the s. file as a dependency for the
526 			 * plain file.
527 			 */
528 			maybe_append_prop(plain_file, sccs_prop)->
529 			  body.sccs.file = file;
530 try_pattern:
531 			MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
532 			if ((pattern != NULL) &&
533 			    amatch(tmp_wcs_buffer, pattern)) {
534 				if (debug_level > 0) {
535 					WCSTOMBS(mbs_buffer, pattern);
536 					(void) printf(gettext("'%s: %s' due to %s expansion\n"),
537 						      line->body.line.target->
538 						      string_mb,
539 						      plain_file->string_mb,
540 						      mbs_buffer);
541 				}
542 				enter_dependency(line, plain_file, false);
543 				result++;
544 			}
545 		}
546 	}
547 	(void) closedir(dir_fd);
548 
549 	return result;
550 }
551 
552 /*
553  *	enter_file_name(name_string, library)
554  *
555  *	Helper function for read_dir().
556  *
557  *	Return value:
558  *				The Name that was entered
559  *
560  *	Parameters:
561  *		name_string	Name of the file we want to enter
562  *		library		The library it is a member of, if any
563  *
564  *	Global variables used:
565  */
566 static Name
567 enter_file_name(wchar_t *name_string, wchar_t *library)
568 {
569 	wchar_t		buffer[STRING_BUFFER_LENGTH];
570 	String_rec	lib_name;
571 	Name		name;
572 	Property	prop;
573 
574 	if (library == NULL) {
575 		name = GETNAME(name_string, FIND_LENGTH);
576 		name->stat.is_file = true;
577 		return name;
578 	}
579 
580 	INIT_STRING_FROM_STACK(lib_name, buffer);
581 	append_string(library, &lib_name, FIND_LENGTH);
582 	append_char((int) parenleft_char, &lib_name);
583 	append_string(name_string, &lib_name, FIND_LENGTH);
584 	append_char((int) parenright_char, &lib_name);
585 
586 	name = GETNAME(lib_name.buffer.start, FIND_LENGTH);
587 	name->stat.is_file = true;
588 	name->is_member = true;
589 	prop = maybe_append_prop(name, member_prop);
590 	prop->body.member.library = GETNAME(library, FIND_LENGTH);
591 	prop->body.member.library->stat.is_file = true;
592 	prop->body.member.entry = NULL;
593 	prop->body.member.member = GETNAME(name_string, FIND_LENGTH);
594 	prop->body.member.member->stat.is_file = true;
595 	return name;
596 }
597 
598 /*
599  *	star_match(string, pattern)
600  *
601  *	This is a regular shell type wildcard pattern matcher
602  *	It is used when xpanding wildcards in dependency lists
603  *
604  *	Return value:
605  *				Indication if the string matched the pattern
606  *
607  *	Parameters:
608  *		string		String to match
609  *		pattern		Pattern to match it against
610  *
611  *	Global variables used:
612  */
613 static Boolean
614 star_match(register wchar_t *string, register wchar_t *pattern)
615 {
616 	register int		pattern_ch;
617 
618 	switch (*pattern) {
619 	case 0:
620 		return succeeded;
621 	case bracketleft_char:
622 	case question_char:
623 	case asterisk_char:
624 		while (*string) {
625 			if (amatch(string++, pattern)) {
626 				return succeeded;
627 			}
628 		}
629 		break;
630 	default:
631 		pattern_ch = (int) *pattern++;
632 		while (*string) {
633 			if ((*string++ == pattern_ch) &&
634 			    amatch(string, pattern)) {
635 				return succeeded;
636 			}
637 		}
638 		break;
639 	}
640 	return failed;
641 }
642 
643 /*
644  *	amatch(string, pattern)
645  *
646  *	Helper function for shell pattern matching
647  *
648  *	Return value:
649  *				Indication if the string matched the pattern
650  *
651  *	Parameters:
652  *		string		String to match
653  *		pattern		Pattern to match it against
654  *
655  *	Global variables used:
656  */
657 static Boolean
658 amatch(register wchar_t *string, register wchar_t *pattern)
659 {
660 	register long		lower_bound;
661 	register long		string_ch;
662 	register long		pattern_ch;
663 	register int		k;
664 
665 top:
666 	for (; 1; pattern++, string++) {
667 		lower_bound = 017777777777;
668 		string_ch = *string;
669 		switch (pattern_ch = *pattern) {
670 		case bracketleft_char:
671 			k = 0;
672 			while ((pattern_ch = *++pattern) != 0) {
673 				switch (pattern_ch) {
674 				case bracketright_char:
675 					if (!k) {
676 						return failed;
677 					}
678 					string++;
679 					pattern++;
680 					goto top;
681 				case hyphen_char:
682 					k |= (lower_bound <= string_ch) &&
683 					     (string_ch <=
684 					      (pattern_ch = pattern[1]));
685 				default:
686 					if (string_ch ==
687 					    (lower_bound = pattern_ch)) {
688 						k++;
689 					}
690 				}
691 			}
692 			return failed;
693 		case asterisk_char:
694 			return star_match(string, ++pattern);
695 		case 0:
696 			return BOOLEAN(!string_ch);
697 		case question_char:
698 			if (string_ch == 0) {
699 				return failed;
700 			}
701 			break;
702 		default:
703 			if (pattern_ch != string_ch) {
704 				return failed;
705 			}
706 			break;
707 		}
708 	}
709 	/* NOTREACHED */
710 }
711 
712