/*
 * CDDL HEADER START
 *
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may use this file only in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.opensource.org/licenses/cddl1.txt
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms.
 */
/*
 * @(#)files.cc 1.37 06/12/12
 */

#pragma	ident	"@(#)files.cc	1.37	06/12/12"

/*
 * Copyright 2017-2020 J. Schilling
 * Copyright 2022 the schilytools team
 *
 * @(#)files.cc	1.11 20/11/19 2017-2020 J. Schilling
 */
#include <schily/mconfig.h>
#ifndef lint
static	UConst char sccsid[] =
	"@(#)files.cc	1.11 20/11/19 2017-2020 J. Schilling";
#endif

/*
 *	files.c
 *
 *	Various file related routines:
 *		Figure out if file exists
 *		Wildcard resolution for directory reader
 *		Directory reader
 */


/*
 * Included files
 */
#include <mk/defs.h>
#include <mksh/macro.h>		/* getvar() */
#include <mksh/misc.h>		/* get_prop(), append_prop() */

/*
 * Defined macros
 */

/*
 * typedefs & structs
 */

/*
 * Static variables
 */

/*
 * File table of contents
 */
extern	timestruc_t&	exists(register Name target);
extern  void		set_target_stat(register Name target, struct stat buf);
static	timestruc_t&	vpath_exists(register Name target);
static	Name		enter_file_name(wchar_t *name_string, wchar_t *library);
static	Boolean		star_match(register wchar_t *string, register wchar_t *pattern);
static	Boolean		amatch(register wchar_t *string, register wchar_t *pattern);

/*
 *	exists(target)
 *
 *	Figure out the timestamp for one target.
 *
 *	Return value:
 *				The time the target was created
 *
 *	Parameters:
 *		target		The target to check
 *
 *	Global variables used:
 *		debug_level	Should we trace the stat call?
 *		recursion_level	Used for tracing
 *		vpath_defined	Was the variable VPATH defined in environment?
 */
timestruc_t&
exists(register Name target)
{
	struct stat		buf;
	register int		result;

	/* We cache stat information. */
	if (target->stat.time != file_no_time) {
		return target->stat.time;
	}

	if (target->stat.is_phony && target->stat.time == file_no_time) {
		target->stat.time = file_phony_time;
		return target->stat.time;
	}

	/*
	 * If the target is a member, we have to extract the time
	 * from the archive.
	 */
	if (target->is_member &&
	    (get_prop(target->prop, member_prop) != NULL)) {
		return read_archive(target);
	}

	if (debug_level > 1) {
		(void) printf(NOCATGETS("%*sstat(%s)\n"),
		              recursion_level,
		              "",
		              target->string_mb);
	}

	result = lstat_vroot(target->string_mb, &buf, NULL, VROOT_DEFAULT);
	if ((result != -1) && ((buf.st_mode & S_IFMT) == S_IFLNK)) {
                /*
		 * If the file is a symbolic link, we remember that
		 * and then we get the status for the refd file.
		 */
                target->stat.is_sym_link = true;
                result = stat_vroot(target->string_mb, &buf, NULL, VROOT_DEFAULT);
        } else {
                target->stat.is_sym_link = false;
	}

	if (result < 0) {
		target->stat.time = file_doesnt_exist;
		target->stat.stat_errno = errno;
		if ((errno == ENOENT) &&
		    vpath_defined &&
/* azv, fixing bug 1262942, VPATH works with a leaf name
 * but not a directory name.
 */
		    (target->string_mb[0] != (int) slash_char) ) {
/* BID_1214655 */
/* azv */
			vpath_exists(target);
			// return vpath_exists(target);
		}
	} else {
		/* Save all the information we need about the file */
		target->stat.stat_errno = 0;
		target->stat.is_file = true;
		target->stat.mode = buf.st_mode & 0777;
		target->stat.size = buf.st_size;
		target->stat.is_dir =
		  BOOLEAN((buf.st_mode & S_IFMT) == S_IFDIR);
		if (target->stat.is_dir) {
			target->stat.time = file_is_dir;
		} else {
			/* target->stat.time = buf.st_mtime; */

#ifdef	stat_mnsecs
			timestruc_t ttime = { buf.st_mtime, stat_mnsecs(&buf) };
			target->stat.time = MAX(ttime, file_min_time);

/* BID_1129806 */
/* vis@nbsp.nsk.su */
#elif defined(linux)
			timestruc_t ttime = { buf.st_mtime, 0 };
			target->stat.time = MAX(ttime, file_min_time);
#else
			target->stat.time = MAX(buf.st_mtim, file_min_time);
#endif
		}
	}
	if ((target->colon_splits > 0) &&
	    (get_prop(target->prop, time_prop) == NULL)) {
		append_prop(target, time_prop)->body.time.time =
		  target->stat.time;
	}
	return target->stat.time;
}

/*
 *	set_target_stat( target, buf)
 *
 *	Called by exists() to set some stat fields in the Name structure
 *	to those read by the stat_vroot() call (from disk).
 *
 *	Parameters:
 *		target		The target whose stat field is set
 *		buf		stat values (on disk) of the file
 *				represented by target.
 */
void
set_target_stat(register Name target, struct stat buf)
{
	target->stat.stat_errno = 0;
	target->stat.is_file = true;
	target->stat.mode = buf.st_mode & 0777;
	target->stat.size = buf.st_size;
	target->stat.is_dir =
	  BOOLEAN((buf.st_mode & S_IFMT) == S_IFDIR);
	if (target->stat.is_dir) {
		target->stat.time = file_is_dir;
	} else {
		/* target->stat.time = buf.st_mtime; */

#ifdef	stat_mnsecs
		timestruc_t ttime = { buf.st_mtime, stat_mnsecs(&buf) };
		target->stat.time = MAX(ttime, file_min_time);
/* BID_1129806 */
/* vis@nbsp.nsk.su */
#elif defined(linux)
		timestruc_t ttime = { buf.st_mtime, 0 };
		target->stat.time = ttime;
#else
		target->stat.time = MAX(buf.st_mtim, file_min_time);
#endif
	}
}


/*
 *	vpath_exists(target)
 *
 *	Called if exists() discovers that there is a VPATH defined.
 *	This function stats the VPATH translation of the target.
 *
 *	Return value:
 *				The time the target was created
 *
 *	Parameters:
 *		target		The target to check
 *
 *	Global variables used:
 *		vpath_name	The Name "VPATH", used to get macro value
 */
static timestruc_t&
vpath_exists(register Name target)
{
	wchar_t			*vpath;
	wchar_t			file_name[MAXPATHLEN];
	wchar_t			*name_p;
	Name			alias;

	/*
	 * To avoid recursive search through VPATH when exists(alias) is called
	 */
	vpath_defined = false;

	Wstring wcb(getvar(vpath_name));
	Wstring wcb1(target);

	vpath = wcb.get_string();

	while (*vpath != (int) nul_char) {
		name_p = file_name;
		while ((*vpath != (int) colon_char) &&
		       (*vpath != (int) nul_char)) {
			*name_p++ = *vpath++;
		}
		*name_p++ = (int) slash_char;
		(void) wcscpy(name_p, wcb1.get_string());
		alias = GETNAME(file_name, FIND_LENGTH);
		if (exists(alias) != file_doesnt_exist) {
			target->stat.is_file = true;
			target->stat.mode = alias->stat.mode;
			target->stat.size = alias->stat.size;
			target->stat.is_dir = alias->stat.is_dir;
			target->stat.time = alias->stat.time;
			maybe_append_prop(target, vpath_alias_prop)->
						body.vpath_alias.alias = alias;
			target->has_vpath_alias_prop = true;
			vpath_defined = true;
			return alias->stat.time;
		}
		while ((*vpath != (int) nul_char) &&
		       ((*vpath == (int) colon_char) || iswspace(*vpath))) {
			vpath++;
		}
	}
	/*
	 * Restore vpath_defined
	 */
	vpath_defined = true;
	return target->stat.time;
}

/*
 *	read_dir(dir, pattern, line, library)
 *
 *	Used to enter the contents of directories into makes namespace.
 *	Presence of a file is important when scanning for implicit rules.
 *	read_dir() is also used to expand wildcards in dependency lists.
 *
 *	Return value:
 *				Non-0 if we found files to match the pattern
 *
 *	Parameters:
 *		dir		Path to the directory to read
 *		pattern		Pattern for that files should match or NULL
 *		line		When we scan using a pattern we enter files
 *				we find as dependencies for this line
 *		library		If we scan for "lib.a(<wildcard-member>)"
 *
 *	Global variables used:
 *		debug_level	Should we trace the dir reading?
 *		dot		The Name ".", compared against
 *		sccs_dir_path	The path to the SCCS dir (from PROJECTDIR)
 *		vpath_defined	Was the variable VPATH defined in environment?
 *		vpath_name	The Name "VPATH", use to get macro value
 */
int
read_dir(Name dir, wchar_t *pattern, Property line, wchar_t *library)
{
	wchar_t			file_name[MAXPATHLEN];
	wchar_t			*file_name_p = file_name;
	Name			file;
	wchar_t			plain_file_name[MAXPATHLEN];
	wchar_t			*plain_file_name_p;
	Name			plain_file;
	wchar_t			tmp_wcs_buffer[MAXPATHLEN];
	DIR			*dir_fd;
#if defined(SUN5_0) || defined(HP_UX)
#define d_fileno d_ino
        register struct dirent  *dp;
#else
        register struct direct  *dp;
#endif
	wchar_t			*vpath = NULL;
	wchar_t			*p;
	int			result = 0;

	if(dir->hash.length >= MAXPATHLEN) {
		return 0;
	}

	Wstring wcb(dir);
	Wstring vps;

	/* A directory is only read once unless we need to expand wildcards. */
	if (pattern == NULL) {
		if (dir->has_read_dir) {
			return 0;
		}
		dir->has_read_dir = true;
	}
	/* Check if VPATH is active and setup list if it is. */
	if (vpath_defined && (dir == dot)) {
		vps.init(getvar(vpath_name));
		vpath = vps.get_string();
	}

	/*
	 * Prepare the string where we build the full name of the
	 * files in the directory.
	 */
	if ((dir->hash.length > 1) || (wcb.get_string()[0] != (int) period_char)) {
		(void) wcscpy(file_name, wcb.get_string());
		MBSTOWCS(wcs_buffer, "/");
		(void) wcscat(file_name, wcs_buffer);
		file_name_p = file_name + wcslen(file_name);
	}

	/* Open the directory. */
vpath_loop:
	dir_fd = opendir(dir->string_mb);
	if (dir_fd == NULL) {
		return 0;
	}

	/* Read all the directory entries. */
	while ((dp = readdir(dir_fd)) != NULL) {
		/* We ignore "." and ".." */
		if ((dp->d_fileno == 0) ||
		    ((dp->d_name[0] == (int) period_char) &&
		     ((dp->d_name[1] == 0) ||
		      ((dp->d_name[1] == (int) period_char) &&
		       (dp->d_name[2] == 0))))) {
			continue;
		}
		/*
		 * Build the full name of the file using whatever
		 * path supplied to the function.
		 */
		MBSTOWCS(tmp_wcs_buffer, dp->d_name);
		(void) wcscpy(file_name_p, tmp_wcs_buffer);
		file = enter_file_name(file_name, library);
		if ((pattern != NULL) && amatch(tmp_wcs_buffer, pattern)) {
			/*
			 * If we are expanding a wildcard pattern, we
			 * enter the file as a dependency for the target.
			 */
			if (debug_level > 0){
				WCSTOMBS(mbs_buffer, pattern);
				(void) printf(gettext("'%s: %s' due to %s expansion\n"),
					      line->body.line.target->string_mb,
					      file->string_mb,
					      mbs_buffer);
			}
			enter_dependency(line, file, false);
			result++;
		} else {
			/*
			 * If the file has an SCCS/s. file,
			 * we will detect that later on.
			 */
			file->stat.has_sccs = NO_SCCS;
		/*
		 * If this is an s. file, we also enter it as if it
		 * existed in the plain directory.
		 */
		if ((dp->d_name[0] == 's') &&
		    (dp->d_name[1] == (int) period_char)) {
	
			MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
			plain_file_name_p = plain_file_name;
			(void) wcscpy(plain_file_name_p, tmp_wcs_buffer);
			plain_file = GETNAME(plain_file_name, FIND_LENGTH);
			plain_file->stat.is_file = true;
			plain_file->stat.has_sccs = HAS_SCCS;
			/*
			 * Enter the s. file as a dependency for the
			 * plain file.
			 */
			maybe_append_prop(plain_file, sccs_prop)->
			  body.sccs.file = file;
			MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
			if ((pattern != NULL) &&
			    amatch(tmp_wcs_buffer, pattern)) {
				if (debug_level > 0) {
					WCSTOMBS(mbs_buffer, pattern);
					(void) printf(gettext("'%s: %s' due to %s expansion\n"),
						      line->body.line.target->
						      string_mb,
						      plain_file->string_mb,
						      mbs_buffer);
				}
				enter_dependency(line, plain_file, false);
				result++;
			}
		}
	      }
	}
	(void) closedir(dir_fd);
	if ((vpath != NULL) && (*vpath != (int) nul_char)) {
		while ((*vpath != (int) nul_char) &&
		       (iswspace(*vpath) || (*vpath == (int) colon_char))) {
			vpath++;
		}
		p = vpath;
		while ((*vpath != (int) colon_char) &&
		       (*vpath != (int) nul_char)) {
			vpath++;
		}
		if (vpath > p) {
			dir = GETNAME(p, vpath - p);
			goto vpath_loop;
		}
	}
/*
 * look into SCCS directory only if it's not svr4. For svr4 dont do that.
 */

/*
 * Now read the SCCS directory.
 * Files in the SCSC directory are considered to be part of the set of
 * files in the plain directory. They are also entered in their own right.
 * Prepare the string where we build the true name of the SCCS files.
 */
	(void) wcsncpy(plain_file_name,
		      file_name,
		      file_name_p - file_name);
	plain_file_name[file_name_p - file_name] = 0;
	plain_file_name_p = plain_file_name + wcslen(plain_file_name);

        if(!svr4) {

	  if (sccs_dir_path != NULL) {
		wchar_t		tmp_wchar;
		wchar_t		path[MAXPATHLEN];
		char		mb_path[MAXPATHLEN];

		if (file_name_p - file_name > 0) {
			tmp_wchar = *file_name_p;
			*file_name_p = 0;
			WCSTOMBS(mbs_buffer, file_name);
			(void) sprintf(mb_path, NOCATGETS("%s/%s/SCCS"),
				        sccs_dir_path,
				        mbs_buffer);
			*file_name_p = tmp_wchar;
		} else {
			(void) sprintf(mb_path, NOCATGETS("%s/SCCS"), sccs_dir_path);
		}
		MBSTOWCS(path, mb_path);
		(void) wcscpy(file_name, path);
	  } else {
		MBSTOWCS(wcs_buffer, NOCATGETS("SCCS"));
		(void) wcscpy(file_name_p, wcs_buffer);
	  }
	} else {
		MBSTOWCS(wcs_buffer, NOCATGETS("."));
		(void) wcscpy(file_name_p, wcs_buffer);
	}
	/* Internalize the constructed SCCS dir name. */
	(void) exists(dir = GETNAME(file_name, FIND_LENGTH));
	/* Just give up if the directory file doesnt exist. */
	if (!dir->stat.is_file) {
		return result;
	}
	/* Open the directory. */
	dir_fd = opendir(dir->string_mb);
	if (dir_fd == NULL) {
		return result;
	}
	MBSTOWCS(wcs_buffer, "/");
	(void) wcscat(file_name, wcs_buffer);
	file_name_p = file_name + wcslen(file_name);

	while ((dp = readdir(dir_fd)) != NULL) {
		if ((dp->d_fileno == 0) ||
		    ((dp->d_name[0] == (int) period_char) &&
		     ((dp->d_name[1] == 0) ||
		      ((dp->d_name[1] == (int) period_char) &&
		       (dp->d_name[2] == 0))))) {
			continue;
		}
		/* Construct and internalize the true name of the SCCS file. */
		MBSTOWCS(wcs_buffer, dp->d_name);
		(void) wcscpy(file_name_p, wcs_buffer);
		file = GETNAME(file_name, FIND_LENGTH);
		file->stat.is_file = true;
		file->stat.has_sccs = NO_SCCS;
		/*
		 * If this is an s. file, we also enter it as if it
		 * existed in the plain directory.
		 */
		if ((dp->d_name[0] == 's') &&
		    (dp->d_name[1] == (int) period_char)) {
	
			MBSTOWCS(wcs_buffer, dp->d_name + 2);
			(void) wcscpy(plain_file_name_p, wcs_buffer);
			plain_file = GETNAME(plain_file_name, FIND_LENGTH);
			plain_file->stat.is_file = true;
			plain_file->stat.has_sccs = HAS_SCCS;
				/* if sccs dependency is already set,skip */
			if(plain_file->prop) {
				Property sprop = get_prop(plain_file->prop,sccs_prop);
				if(sprop != NULL) {
					if (sprop->body.sccs.file) {
						goto try_pattern;
					}
				}
			}

			/*
			 * Enter the s. file as a dependency for the
			 * plain file.
			 */
			maybe_append_prop(plain_file, sccs_prop)->
			  body.sccs.file = file;
try_pattern:
			MBSTOWCS(tmp_wcs_buffer, dp->d_name + 2);
			if ((pattern != NULL) &&
			    amatch(tmp_wcs_buffer, pattern)) {
				if (debug_level > 0) {
					WCSTOMBS(mbs_buffer, pattern);
					(void) printf(gettext("'%s: %s' due to %s expansion\n"),
						      line->body.line.target->
						      string_mb,
						      plain_file->string_mb,
						      mbs_buffer);
				}
				enter_dependency(line, plain_file, false);
				result++;
			}
		}
	}
	(void) closedir(dir_fd);

	return result;
}

/*
 *	enter_file_name(name_string, library)
 *
 *	Helper function for read_dir().
 *
 *	Return value:
 *				The Name that was entered
 *
 *	Parameters:
 *		name_string	Name of the file we want to enter
 *		library		The library it is a member of, if any
 *
 *	Global variables used:
 */
static Name
enter_file_name(wchar_t *name_string, wchar_t *library)
{
	wchar_t		buffer[STRING_BUFFER_LENGTH];
	String_rec	lib_name;
	Name		name;
	Property	prop;

	if (library == NULL) {
		name = GETNAME(name_string, FIND_LENGTH);
		name->stat.is_file = true;
		return name;
	}

	INIT_STRING_FROM_STACK(lib_name, buffer);
	append_string(library, &lib_name, FIND_LENGTH);
	append_char((int) parenleft_char, &lib_name);
	append_string(name_string, &lib_name, FIND_LENGTH);
	append_char((int) parenright_char, &lib_name);

	name = GETNAME(lib_name.buffer.start, FIND_LENGTH);
	name->stat.is_file = true;
	name->is_member = true;
	prop = maybe_append_prop(name, member_prop);
	prop->body.member.library = GETNAME(library, FIND_LENGTH);
	prop->body.member.library->stat.is_file = true;
	prop->body.member.entry = NULL;
	prop->body.member.member = GETNAME(name_string, FIND_LENGTH);
	prop->body.member.member->stat.is_file = true;
	return name;
}

/*
 *	star_match(string, pattern)
 *
 *	This is a regular shell type wildcard pattern matcher
 *	It is used when xpanding wildcards in dependency lists
 *
 *	Return value:
 *				Indication if the string matched the pattern
 *
 *	Parameters:
 *		string		String to match
 *		pattern		Pattern to match it against
 *
 *	Global variables used:
 */
static Boolean
star_match(register wchar_t *string, register wchar_t *pattern)
{
	register int		pattern_ch;

	switch (*pattern) {
	case 0:
		return succeeded;
	case bracketleft_char:
	case question_char:
	case asterisk_char:
		while (*string) {
			if (amatch(string++, pattern)) {
				return succeeded;
			}
		}
		break;
	default:
		pattern_ch = (int) *pattern++;
		while (*string) {
			if ((*string++ == pattern_ch) &&
			    amatch(string, pattern)) {
				return succeeded;
			}
		}
		break;
	}
	return failed;
}

/*
 *	amatch(string, pattern)
 *
 *	Helper function for shell pattern matching
 *
 *	Return value:
 *				Indication if the string matched the pattern
 *
 *	Parameters:
 *		string		String to match
 *		pattern		Pattern to match it against
 *
 *	Global variables used:
 */
static Boolean
amatch(register wchar_t *string, register wchar_t *pattern)
{
	register long		lower_bound;
	register long		string_ch;
	register long		pattern_ch;
	register int		k;

top:
	for (; 1; pattern++, string++) {
		lower_bound = 017777777777;
		string_ch = *string;
		switch (pattern_ch = *pattern) {
		case bracketleft_char:
			k = 0;
			while ((pattern_ch = *++pattern) != 0) {
				switch (pattern_ch) {
				case bracketright_char:
					if (!k) {
						return failed;
					}
					string++;
					pattern++;
					goto top;
				case hyphen_char:
					k |= (lower_bound <= string_ch) &&
					     (string_ch <=
					      (pattern_ch = pattern[1]));
					/* FALLTHRU */
				default:
					if (string_ch ==
					    (lower_bound = pattern_ch)) {
						k++;
					}
				}
			}
			return failed;
		case asterisk_char:
			return star_match(string, ++pattern);
		case 0:
			return BOOLEAN(!string_ch);
		case question_char:
			if (string_ch == 0) {
				return failed;
			}
			break;
		default:
			if (pattern_ch != string_ch) {
				return failed;
			}
			break;
		}
	}
	/* NOTREACHED */
}

