#!mkcmd
# $Id: glob.m,v 1.8 2012/07/08 21:29:42 ksb Exp $
# I sick of ls telling me the argument list is too long.  I want to	(ksb)
# glob a huge list of files or directories to send to xapply -f, and I
# don't want a limit on the size of the list -- thanks.
#
# Shamelessly copied from my mkcmd exampe-glob.m, and util_fgetln, then
# stirred and enhanced a bit. -- ksb
#
#          $Compile: mkcmd -n %F %f && %b %o -m%m %F.c
comment "* %kCompile: %k%{cc-cc%} -o %%F %%f %k%k"

from '<stdio.h>'
from '<glob.h>'		# must be above machine.h
from '<sysexits.h>'

require "util_glob.m" "glob_ieee.m"
require "std_help.m" "std_version.m"
basename "glob" ""

key "glob_options" 1 {
	"wGlobFlags"
}
from '"machine.h"'
augment action 'V' {
	user "Version();"
}

%i
static char rcsid[] =
	"$Id: glob.m,v 1.8 2012/07/08 21:29:42 ksb Exp $";

#ifdef MKCMD_BROKEN_ALT_ENDING
static int fBugZZZ;	/* mkcmd 8.16 was is broken! 8.17 works */
#endif

/* A command-line option to turn on/off bits in this parmeter would
 * be really nice, but some of the options are not available on all
 * platforms and I don't know what to do about that. --ksb
 * If you change this you might need to update Version() below.
 */
static int
	wGlobFlags = LOCAL_GLOB_FLAGS,	/* flags for IEEE glob		*/
	cSentinal = '\n';		/* or \000 under -z		*/
%%

boolean 'f' {
	named "fFilter"
	help "glob names from a filter, rather than from command-line patterns"
	key "f_filter_file" 2 initialize {
		"%rfR<every>n" "%n" "%N"
	}
	every function {
		named "Filter"
		file variable "fpIn" "every" {
			local
		}
		update "%r{fpIn}N = %N;%r{fpIn}Xxu"
		user "%r{fpIn}XK<f_filter_file>/ef;"
		track "fBugZZZ"
		param "files"
		help "input files containing glob expressions"
	}

	key "f_filter_pipe" 2 initialize {
		"%rfR<every>n" "stdin" '"-"'
	}
	zero {
		update '%K<f_filter_pipe>/ef;'
		help "process stdin"
	}
}
boolean 'r' {
	named "fVetch"
	help "report non-matching patterns on stderr, rather than passing them along unmatched"
}
boolean 's' {
	named "fSilent"
	help "unmatched expressions are completely silent, ignore -r"
}
action 'z' {
	update "cSentinal = '\\000';"
	help "output names with terminating NUL characters, like find -print0"
}
type "mode" 'm' {
	named "pMCMatch"
	track "fGaveMode"
	help "the matched files must match the given mode"
}

every {
	named "Matches"
	track "fGaveLit"
	param "glob"
	help "output matches for each expression"
}

%c
/* Explain some internal compile options				(ksb)
 * In this program we try to explain the glob options we set, but if
 * the customer didn't read glob.3 this won't help a lot.
 */
static void
Version()
{
	register char *pcSep = "", *pcNext = "|";

	printf("%s: glob(3) flags ", progname);
#if defined(GLOB_NOSORT)
	if (0 != (wGlobFlags&GLOB_NOSORT)) {
		printf("%sNOSORT", pcSep);
		pcSep = pcNext;
	}
#endif
#if defined(GLOB_TILDE)
	if (0 != (wGlobFlags&GLOB_TILDE)) {
		printf("%sTILDE", pcSep);
		pcSep = pcNext;
	}
#endif
#if defined(GLOB_QUOTE)
	if (0 != (wGlobFlags&GLOB_QUOTE)) {
		printf("%sQUOTE", pcSep);
		pcSep = pcNext;
	}
#endif
#if defined(GLOB_MARK)
	if (0 != (wGlobFlags&GLOB_MARK)) {
		printf("%sQUOTE", pcSep);
		pcSep = pcNext;
	}
#endif
#if defined(GLOB_NOESCAPE)
	if (0 != (wGlobFlags&GLOB_NOESCAPE)) {
		printf("%sNOESCAPE", pcSep);
		pcSep = pcNext;
	}
#endif
#if defined(GLOB_NOESCAPE)
	if (0 != (wGlobFlags&GLOB_NOESCAPE)) {
		printf("%sNOESCAPE", pcSep);
		pcSep = pcNext;
	}
#endif
#if defined(GLOB_BRACE)
	if (0 != (wGlobFlags&GLOB_BRACE)) {
		printf("%sBRACE", pcSep);
		pcSep = pcNext;
	}
#endif
	printf("\n");
}

/* The proposed file must have the correct type,			(ksb)
 * all the mode bits set,
 * and non other than the maditory|optional bits set
 */
char **
ModeMatch(char **ppcMay, u_MODE_CVT *pMCMatch)
{
	register char **ppcScan;
	register int iFound;
	auto char **ppcRet;
	auto struct stat stLook;
	static char **ppcHold;
	static int iMax = 0;

	/* standard reset logic, free our internal buffer
	 */
	if ((char **)0 == ppcMay) {
		if ((char **)0 != ppcHold) {
			free((void *)ppcHold);
			iMax = 0;
			ppcHold = (char **)0;
		}
		return (char **)0;
	}
	if (0 == iMax) {
		iMax = 8192;
		while (0 != iMax && (char **)0 == (ppcHold = calloc(iMax, sizeof(char *))))
			iMax >>= 1;
		if (0 == iMax) {
			fprintf(stderr, "%s: calloc: no memory available\n", progname);
			exit(EX_OSERR);
		}
	}

	iFound = 0;
	ppcRet = ppcMay;
	for (ppcScan = ppcMay; (char *)0 != *ppcScan; ++ppcScan) {
		if (-1 == lstat(*ppcScan, &stLook))
			continue; /* raced with an unlink? */
		if ('\000' != pMCMatch->cfmt && pMCMatch->iifmt != (S_IFMT&stLook.st_mode))
			continue;
		if (pMCMatch->iset != (pMCMatch->iset & stLook.st_mode))
			continue;
		if (0 != ((07777 & stLook.st_mode) &~ (pMCMatch->iset|pMCMatch->ioptional)))
			continue;

		if (iFound == iMax) {
			register int iAdd = 16384;
			register char **ppcAlloc;

			while (0 != iAdd && (char **)0 == (ppcAlloc = realloc(ppcHold, (iMax+iAdd)*sizeof(char *))))
				iAdd >>= 1;
			if (0 == iAdd) {
				fprintf(stderr, "%s: realloc: out of memory\n", progname);
				exit(EX_OSERR);
			}
			iMax += iAdd;
			ppcHold = ppcAlloc;
		}
		ppcHold[iFound++] = *ppcScan;
	}
	ppcHold[iFound] = (char *)0;
	return 0 == iFound ? (char **)0 : ppcHold;
}

/* match a single expression						(ksb)
 */
static void
Matches(char *pcIt)
{
	register char **ppcFound, **ppcReport;
	register int i;

	if ((char **)0 != (ppcReport = ppcFound = util_glob(pcIt)) && fGaveMode) {
		ppcReport = ModeMatch(ppcFound, pMCMatch);
	}
	if ((char **)0 == ppcReport) {
		if (fSilent) {
			/* nada */
		} else if (fVetch) {
			fprintf(stderr, "%s: %s: no match\n", progname, pcIt);
		} else {
			printf("%s%c", pcIt, cSentinal);
		}
		return;
	}
	for (i = 0; (char *)0 != ppcReport[i]; ++i) {
		printf("%s%c", ppcReport[i], cSentinal);
	}
	util_glob_free(ppcFound);
}

/* Read some file for the glob expressions				(ksb)
 */
static void
Filter(FILE *fp, char *pcName)
{
	static char *pcFGBuf = (char *)0;
	static size_t wLen = 0;
	register int c;
	register size_t iRead;
	register char *pc;	/* realloc test */

	if (0 == wLen) {
		wLen = 4096;
		pcFGBuf = calloc(wLen, sizeof(char));
	}

	/* inv. the buffer has room for the next character at least
	 */
	for (iRead = 0; EOF != (c = getc(fp)); /* nothing */) {
		if (cSentinal == c) {
			pcFGBuf[iRead] = '\000';
			Matches(pcFGBuf);
			iRead = 0;
			continue;
		}
		pcFGBuf[iRead++] = c;
		if (iRead < wLen) {
			continue;
		}
		wLen += 1024;
		pc = realloc(pcFGBuf, wLen * sizeof(char));
		if ((char *)0 == pc) {
			fprintf(stderr, "%s: realloc: %lu: %s\n", progname, (unsigned long)wLen, strerror(errno));
			exit(EX_OSERR);
		}
		pcFGBuf = pc;
	}
	if (0 != iRead) {
		pcFGBuf[iRead] = '\000';
		Matches(pcFGBuf);
	}
}
