// APPEND TOP
/* lib -- irqtune support library */

#pragma member irqtune.h

// APPEND libcmd.c
/* cmdopen -- open command pipe */
/* RETURNS: stream pointer */
struct cmdblk *
cmdopen(const char *bf)
{
	int ok;
	int err;
	int unit;
	int idx;
	int argc;
	struct cmdblk *cmd;
	char *argv[100];

	do {
		/* allocate a new command block */
		ok = 0;
		for (CMDFOR(cmd)) {
			if (! (cmd->cmd_opt & CMDBUSY)) {
				ok = 1;
				break;
			}
		}
		if (! ok) {
			fprintf(stderr,"cmdopen: unable to locate cmdblk\n");
			break;
		}
		memset(cmd,0,sizeof(struct cmdblk));
		cmd->cmd_opt |= CMDBUSY;

		/* enable signal handler */
		cmdsigon();

		/* copy command to buffer */
		strcpy(cmd->cmd_bf,bf);

		/* create a pipe */
		err = pipe(cmd->cmd_units);
		if (err < 0) {
			fprintf(stderr,"cmdopen: unable to create pipe -- %s\n",
				strerror(errno));
			cmd->cmd_units[0] = -1;
			cmd->cmd_units[1] = -1;
			break;
		}

		/* fork a child */
		cmd->cmd_pid = fork();
		if (cmd->cmd_pid < 0) {
			fprintf(stderr,"cmdopen: unable to fork -- %s\n",strerror(errno));
			break;
		}

		/* handle parent */
		if (cmd->cmd_pid > 0) {
			ok = 1;
			close(cmd->cmd_units[1]);
			break;
		}

		/* close parent's unit */
		close(cmd->cmd_units[0]);

		/* open our stuff on the write units */
		for (unit = 1;  unit <= 2;  ++unit) {
			switch (unit) {
			case 1:
				fclose(stdout);
				break;
			case 2:
				fclose(stderr);
				break;
			}

			/* force it onto the correct unit */
			err = dup2(cmd->cmd_units[1],unit);
			if (err < 0)
				exit(errno);

			/* preserve it across exec */
			err = fcntl(F_SETFD,unit,0);
			if (err < 0)
				exit(errno);
		}

		/* close pipe units now that they've been dup'ed */
		for (idx = 0;  idx <= 1;  ++idx)
			close(cmd->cmd_units[idx]);

		/* split the arguments */
		argc = xstrargv(argv,Cntof(argv),cmd->cmd_bf);

		/* execute the program */
		err = execvp(argv[0],argv);
		if (err < 0)
			exit(errno);
	} while (0);

	/* kill off stuff if error */
	if (! ok) {
		cmdclose(cmd);
		cmd = NULL;
	}

	return cmd;
}

/* cmdclose -- close command pipe */
/* RETURNS: final status */
int
cmdclose(struct cmdblk *cmd)
{
	int status;
	int err;
	char bf[1000];

	status = -1;
	do {
		if (cmd == NULL)
			break;

		/* autoreap the child cleanly */
		while (1) {
			if (cmdread(cmd,bf,sizeof(bf)) < 0)
				break;
		}

		/* get status */
		status = cmd->cmd_status;

		/* close open pipe */
		if (cmd->cmd_units[0] >= 0) {
			close(cmd->cmd_units[0]);
			cmd->cmd_units[0] = -1;
		}

		/* release storage */
		cmd->cmd_opt &= ~CMDBUSY;

		/* release signal handler */
		cmdsigoff();
	} while (0);

	return status;
}

/* cmdcoredump -- decide if program terminated due to core dump */
/* RETURNS: 0=normal, 1=limited, 2=dumped */
int
cmdcoredump(int status)
{
	int err;

	err = 0;
	do {
		/* all core dumps are done via the signal mechanism */
		if (! WIFSIGNALED(status))
			break;

		switch (WTERMSIG(status)) {
		case SIGQUIT:
		case SIGILL:
		case SIGTRAP:
		case SIGIOT:
		case SIGFPE:
		case SIGSEGV:
			err = 1;
			break;
		}
		if (! err)
			break;

		/* FIXME -- WCOREDUMP only defined if __USE_BSD? */
		if (WCOREDUMP(status))
			err = 2;
	} while (0);

	return err;
}

/* cmdread -- read in command line */
/* RETURNS: length (-1=EOF or error) */
int
cmdread(struct cmdblk *cmd,char *bf,int bflen)
{
	int err;
	int totlen;
	int unit;
	char *bp;
	char *bfe;
	fd_set selmsk;
	struct timeval tv;

	bp = bf;
	bfe = bp + bflen - 1;

	totlen = 0;
	unit = cmd->cmd_units[0];
	for (;  bp < bfe;  ++bp, ++totlen) {
		/* try to reap the child */
		if (cmd->cmd_pid > 0) {
			err = waitpid(cmd->cmd_pid,&cmd->cmd_status,WNOHANG);
			if (err < 0) {
				fprintf(stderr,"cmdread: wait failure -- %s\n",strerror(errno));
				totlen = -1;
				break;
			}

			/* ok got it */
			if (err > 0)
				cmd->cmd_pid = -1;
		}

		/* wait for data */
		FD_ZERO(&selmsk);
		FD_SET(unit,&selmsk);
		err = select(unit + 1,&selmsk,NULL,NULL,NULL);
		if (err < 0) {
			if (errno == EINTR)
		}

		/* get a character */
		err = read(unit,bp,1);
		if (err < 0) {
			fprintf(stderr,"cmdread: error -- %s\n",strerror(errno));
			totlen = -1;
			break;
		}

		/* handle an EOF */
		if (err == 0) {
			if (totlen <= 0)
				totlen = -1;
			break;
		}

		/* stop when we've got a line */
		if (*bp == '\n')
			break;
	}

	/* close off buffer */
	if (totlen < 0)
		bp = bf;
	*bp = 0;

	return totlen;
}

/* cmdsigon -- enable signal handler */
void
cmdsigon(void)
{
	sigset_t sigmsk;

	/* attach signal handler for SIGCHLD */
	if (cmdsigcnt++ == 0) {
		/* prepare */
		sigemptyset(&sigmsk);
		sigaddset(&sigmsk,SIGCHLD);
		newact.sa_handler = cmdsighdr;
		newact.sa_mask = sigmask(SIGCHLD);
		newact.sa_flags = SA_RESTART;

		/* block the signals before setting signal handler */
		err = sigprocmask(SIG_BLOCK,&sigmsk,&cmdsigmsksv);
		if (err < 0) {
			fprintf(stderr,"cmdclose: unable to sigprocmask -- %s\n",
			strerror(errno);
		}

		/* set handler */
		err = sigaction(SIGCHLD,&newact,&cmdsigactsv);
		if (err < 0) {
			fprintf(stderr,"cmdclose: unable to sigaction -- %s\n",
				strerror(errno);
		}

		/* unblock the signals after setting signal handler */
		err = sigprocmask(SIG_UNBLOCK,&sigmsk,NULL);
		if (err < 0) {
			fprintf(stderr,"cmdclose: unable to sigprocmask -- %s\n",
			strerror(errno);
		}
	}
}

/* cmdsigoff -- disable signal handler */
void
cmdsigoff(void)
{
	int err;

	/* detach signal handler for SIGCHLD */
	if (--cmdsigcnt == 0) {
		err = sigaction(SIGCHLD,&cmdsigactsv,NULL);
		if (err < 0) {
			fprintf(stderr,"cmdclose: unable to sigaction -- %s\n",
			strerror(errno);
		}

		/* restore original block mask */
		err = sigprocmask(SIG_SETMASK,&cmdsigmsksv,NULL);
		if (err < 0) {
			fprintf(stderr,"cmdclose: unable to sigaction -- %s\n",
			strerror(errno);
		}
	}
}

/* cmdsighdr -- command signal handler */
void
cmdsighdr(int signo)
{
	struct cmdblk *cmd;
	int status;
	int pid;

	switch (signo) {
	case SIGCHLD:
		pid = wait(&status);
		for (CMDFOR(cmd)) {
			if (! cmd->cmd_opt & CMDBUSY))
				continue;

			/* reap the child */
			if (cmd->cmd_pid == pid) {
				cmd->cmd_status = status;
				cmd->cmd_pid = 0;
				break;
			}
		}
		break;
	}
}

// APPEND libfile.c
/* filecpy -- copy file */
/* RETURNS: error code */
int
filecpy(int goflg,const char *dirdst,const char *dirsrc,const char *tail)
{
	int perm;
	int srcunit;
	int dstunit;
	int err;
	int rlen;
	struct stat stdst;
	struct stat stsrc;
	struct stat stself;
	char dstfile[1024];
	char srcfile[1024];

	do {
		/* insure self exists (and it's a directory) */
		err = stat(dirtop,&stself);
		if (err < 0) {
			fprintf(stderr,"filecpy: unable to stat self '%s' -- %s\n",
				dirtop,strerror(errno));
			break;
		}
		if (! S_ISDIR(stself.st_mode)) {
			fprintf(stderr,"filecpy: self is not a directory '%s'\n",
				dirtop);
			err = -1;
			break;
		}

		/* insure destination exists (and it's a directory) */
		err = stat(dirdst,&stdst);
		if (err < 0) {
			fprintf(stderr,"filecpy: unable to stat destination '%s' -- %s\n",
				dirdst,strerror(errno));
			break;
		}
		if (! S_ISDIR(stdst.st_mode)) {
			fprintf(stderr,"filecpy: destination is not a directory '%s'\n",
				dirdst);
			err = -1;
			break;
		}

		/* insure self/destination directories are different */
		if ((stdst.st_dev == stself.st_dev) &&
			(stdst.st_ino == stself.st_ino)) {
			fprintf(stderr,"filecpy: self '%s' and destination '%s' are the same\n",
				dirtop,dirdst);
			err = -1;
			break;
		}

		if (dirsrc != NULL) {
			/* insure source exists (and it's a directory) */
			err = stat(dirsrc,&stsrc);
			if (err < 0) {
				fprintf(stderr,"filecpy: unable to stat source '%s' -- %s\n",
					dirsrc,strerror(errno));
				break;
			}
			if (! S_ISDIR(stsrc.st_mode)) {
				fprintf(stderr,"filecpy: source is not a directory '%s'\n",
					dirdst);
				err = -1;
				break;
			}

			/* insure source/destination directories are different */
			if ((stdst.st_dev == stsrc.st_dev) &&
				(stdst.st_ino == stsrc.st_ino)) {
				fprintf(stderr,"filecpy: source '%s' and destination '%s' are the same\n",
					dirsrc,dirdst);
				err = -1;
				break;
			}
		}
	} while (0);

	dstunit = -1;
	srcunit = -1;
	do {
		/* bug out if error */
		if (err < 0)
			break;

		/* bug out if nogo mode */
		if (! goflg)
			break;

		/* show files we're copying */
		sprintf(dstfile,"%s/%s",dirdst,tail);
		fprintf(stderr,"filecpy: %s",dstfile);
		if (dirsrc != NULL) {
			sprintf(srcfile,"%s/%s",dirsrc,tail);
			fprintf(stderr," <-- %s",srcfile);
		}
		fprintf(stderr," ...\n");

		/* get permissions (mimic source if possible) */
		perm = 0755;
		if (dirsrc != NULL)
			perm = stsrc.st_mode & 0777;

		/* open/create the destination file */
		dstunit = open(dstfile,O_WRONLY | O_CREAT | O_TRUNC,perm);
		if (dstunit < 0) {
			fprintf(stderr,"filecpy: unable to open destination '%s' -- %s\n",
				dstfile,strerror(errno));
			err = -1;
			break;
		}

		/* force correct permissions (in case of a prior bogus install) */
		err = fchmod(dstunit,perm);
		if (err < 0) {
			fprintf(stderr,"install: unable change permissions on '%s' -- %s\n",
				dstfile,strerror(errno));
			break;
		}

		/* just open the destination for caller */
		if (dirsrc == NULL)
			break;

		/* open the source */
		srcunit = open(srcfile,O_RDONLY);
		if (srcunit < 0) {
			fprintf(stderr,"filecpy: unable to open source '%s' -- %s\n",
				srcfile,strerror(errno));
			break;
		}

		/* copy the data */
		err = 0;
		while (1) {
			/* read a chunk */
			rlen = read(srcunit,bigbf,sizeof(bigbf));
			if (rlen < 0) {
				fprintf(stderr,"filecpy: read error on '%s' -- %s\n",
					srcfile,strerror(errno));
				err = -1;
				break;
			}
			if (rlen == 0)
				break;

			/* write a chunk */
			err = write(dstunit,bigbf,rlen);
			if (err < 0) {
				fprintf(stderr,"filecpy: write error on '%s' -- %s\n",
					dstfile,strerror(errno));
				break;
			}
		}
	} while (0);

	/* close units */
	if ((dirsrc != NULL) || (err < 0)) {
		if (dstunit >= 0)
			close(dstunit);
		if (srcunit >= 0)
			close(srcunit);
	}

	/* return open destination unit to caller */
	else
		err = dstunit;

	return err;
}

/* filehead -- get head of name */
/* RETURNS: pointer to head */
char *
filehead(char *name)
/* name -- name to get head of */
{
	char *head;

	head = strrchr(name,'/');
	if (head != NULL)
		*head = 0;

	return name;
}

/* filetail -- get tail of name */
/* RETURNS: pointer to tail */
char *
filetail(char *name)
/* name -- name to get tail of */
{
	char *tail;

	tail = strrchr(name,'/');
	if (tail != NULL)
		++tail;
	else
		tail = name;

	return tail;
}

// APPEND libopt.c
/* optget -- get options */
/* RETURNS: 1=ok, 0=error */
int
optget(struct optblk *opt,char *cp)
{
	int ok;

	ok = 0;
	for (;  *cp != 0;  ++cp) {
		for (;  opt->opt_fmt != NULL;  ++opt) {
			if (opt->opt_fmt[0] != '-')
				continue;
			if (*cp == opt->opt_fmt[1])
				break;
		}

		/* bug out if no match */
		if (opt->opt_fmt == NULL) {
			ok = 0;
			break;
		}

		mstopt |= opt->opt_msk;
		ok = 1;

		/* handle single valued option */
		if (opt->opt_ptr != NULL) {
			++cp;
			*opt->opt_ptr = cp;
			break;
		}
	}

	return ok;
}

/* optusage -- show option usage */
void
optusage(const struct optblk *opt)
{

	for (;  opt->opt_fmt != NULL;  ++opt) {
		if (opt->opt_fmt[0] == '-')
			fprintf(stderr,"  %s\n",opt->opt_fmt);
		else
			fprintf(stderr,"%s options:\n",opt->opt_fmt);
	}
}

// APPEND libfile.c
/* pathdir -- insure a directory is in $PATH */
/* RETURNS: pointer to $PATH match (or NULL) */
char *
pathdir(const char *dir)
{
	char *bp;
	char *cp;

	/* get path value */
	cp = getenv("PATH");
	strcpy(pathtmp,cp);

	bp = pathtmp;
	while (1) {
		/* get next element of the path */
		cp = strtok(bp,":");
		if (cp == NULL)
			break;
		bp = NULL;

		/* bug out if we found it */
		if (strcmp(dir,cp) == 0)
			break;
	}

	/* add directory to path (this is the probable place for insmod et. al.) */
	if (cp == NULL) {
		cp = getenv("PATH");
		sprintf(pathtmp,"PATH=%s:%s",cp,dir);

		/* NOTE: putenv won't strdup (e.g. pathbf must stay stable) */
		strcpy(pathbf,pathtmp);
		putenv(pathbf);
	}

	return cp;
}

/* pathlook -- lookup file in $PATH */
/* RETURNS: pointer to directory name (or NULL) */
char *
pathlook(const char *tail,const char *dir)
/* tail -- file to locate */
{
	char *cp;
	char *bp;
	int err;
	struct stat mystat;
	char file[1024];

	/* create path to program */
	if (dir == NULL)
		dir = getenv("PATH");
	strcpy(pathtmp,dir);

	bp = pathtmp;
	while (1) {
		/* get next element of the path */
		cp = strtok(bp,":");
		if (cp == NULL)
			break;
		bp = NULL;

		/* create file name */
		sprintf(file,"%s/%s",cp,tail);

		/* ignore nonexistent file */
		err = stat(file,&mystat);
		if (err < 0)
			continue;

		/* only want regular files */
		if (! S_ISREG(mystat.st_mode))
			continue;

		/* it must be executable by us */
		err = access(file,X_OK);
		if (err >= 0)
			break;
	}

	/* copy result to stable area */
	if (cp != NULL) {
		strcpy(bigbf,cp);
		cp = bigbf;
	}

	return cp;
}

// APPEND libcmd.c
/* vsystem -- execute program */
/* RETURNS: error code */
int
vsystem(int verbose,const char *tag0,const char *tag1,const char *tag2,const char *bf0)
/* tag0 -- primary command (e.g. loading, unloading, etc.) */
/* tag1 -- primary tag */
/* tag2 -- secondary tag */
/* bf0 -- actual command buffer */
{
	char *logf;
	char *cp;
	int code;
	int status;
	struct cmdblk *cmd;
	char bf[4096];

	/* copy command to buffer */
	strcpy(bf,bf0);

	/* show blah_blah_blah only if full errors or verbosity enabled */
	logf = "/dev/null";
	if (! (verbose || (mstopt & OPTVERBOSE))) {
		cp = &bf[strlen(bf)];
		sprintf(cp," >> %s 2>&1",logf);
	}

	/* show stuff beforehand */
	if (mstopt & OPTERR)
		fprintf(stderr,"irqtune: trying command -- %s\n",bf0);
	else {
		fprintf(stderr,"irqtune: %s",tag0);
		if (tag1 != NULL)
			fprintf(stderr," %s",tag1);
		if (tag2 != NULL)
			fprintf(stderr," (%s)",tag2);
		fprintf(stderr,"\n");
	}

	/* get command status */
	cmd = cmdopen(bf);
	status = cmdclose(cmd);

	/* check the status */
	code = 1;
	do {
		/* handle exec error */
		if (status < 0) {
			fprintf(stderr,"irqtune: exec failed of '%s' -- %s\n",
				bf0,strerror(errno));
			break;
		}

		/* handle semi-normal program termination */
		if (WIFEXITED(status)) {
			code = WEXITSTATUS(status);
			code = BOOL(code);
		}
		if (! code)
			break;

		/* show a short, sweet, to the point error message */
		cp = strtok(bf," \t");
		cp = filetail(cp);
		fprintf(stderr,"irqtune: %s failed on '%s'",cp,tag1);

		/* show the really nasty core dumps */
		code = cmdcoredump(status);
		switch (code) {
		case 1:
			fprintf(stderr," (crashed -- core dump not taken)");
			break;
		case 2:
			fprintf(stderr," (crashed -- core dumped)");
			break;
		}

		/* show the absolute raw status */
		if (mstopt & (OPTERR | OPTVERBOSE))
			fprintf(stderr," status=%8.8X",cmd->cmd_status);

		code = 1;
	} while (0);
	if (code)
		fprintf(stderr,"\n");

	return code;
}

// APPEND libstr.c
/* xstrargv -- split buffer into tokens */
/* RETURNS: number of tokens */
int
xstrargv(char **argv,int argc,char *bf)
{
	int argcnt;
	char *cp;

	/* place for NULL */
	--argc;

	for (argcnt = 0;  argc > 0;  --argc, ++argcnt) {
		cp = strtok(bf," \t");
		if (cp == NULL)
			break;

		argv[argcnt] = cp;
		bf = NULL;
	}
	argv[argcnt] = NULL;

	return argcnt;
}
