1872b698pfg/*-
2872b698pfg * SPDX-License-Identifier: BSD-3-Clause
3872b698pfg *
4862fdf1rgrimes * Copyright (c) 1983, 1993
5862fdf1rgrimes *	The Regents of the University of California.  All rights reserved.
6862fdf1rgrimes *
7862fdf1rgrimes *
8862fdf1rgrimes * Redistribution and use in source and binary forms, with or without
9862fdf1rgrimes * modification, are permitted provided that the following conditions
10862fdf1rgrimes * are met:
11862fdf1rgrimes * 1. Redistributions of source code must retain the above copyright
12862fdf1rgrimes *    notice, this list of conditions and the following disclaimer.
13862fdf1rgrimes * 2. Redistributions in binary form must reproduce the above copyright
14862fdf1rgrimes *    notice, this list of conditions and the following disclaimer in the
15862fdf1rgrimes *    documentation and/or other materials provided with the distribution.
167e6cabdimp * 3. Neither the name of the University nor the names of its contributors
17862fdf1rgrimes *    may be used to endorse or promote products derived from this software
18862fdf1rgrimes *    without specific prior written permission.
19862fdf1rgrimes *
20862fdf1rgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21862fdf1rgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22862fdf1rgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23862fdf1rgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24862fdf1rgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25862fdf1rgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26862fdf1rgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27862fdf1rgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28862fdf1rgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29862fdf1rgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30862fdf1rgrimes * SUCH DAMAGE.
31862fdf1rgrimes */
32862fdf1rgrimes
33862fdf1rgrimes#ifndef lint
34bf21e6bwollmanstatic const char copyright[] =
35862fdf1rgrimes"@(#) Copyright (c) 1983, 1993\n\
36862fdf1rgrimes	The Regents of the University of California.  All rights reserved.\n";
37862fdf1rgrimes#endif /* not lint */
38862fdf1rgrimes
395ecf206gad#if 0
40862fdf1rgrimes#ifndef lint
41914cbcejoergstatic char sccsid[] = "@(#)cmds.c	8.2 (Berkeley) 4/28/95";
42862fdf1rgrimes#endif /* not lint */
435ecf206gad#endif
445ecf206gad
455ecf206gad#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
465ecf206gad__FBSDID("$FreeBSD$");
47862fdf1rgrimes
48862fdf1rgrimes/*
49862fdf1rgrimes * lpc -- line printer control program -- commands:
50862fdf1rgrimes */
51862fdf1rgrimes
52862fdf1rgrimes#include <sys/param.h>
53862fdf1rgrimes#include <sys/time.h>
54862fdf1rgrimes#include <sys/stat.h>
55914cbcejoerg#include <sys/file.h>
56862fdf1rgrimes
57862fdf1rgrimes#include <signal.h>
58862fdf1rgrimes#include <fcntl.h>
594bc19b0eadler#include <err.h>
60862fdf1rgrimes#include <errno.h>
61862fdf1rgrimes#include <dirent.h>
62862fdf1rgrimes#include <unistd.h>
63862fdf1rgrimes#include <stdlib.h>
64862fdf1rgrimes#include <stdio.h>
65862fdf1rgrimes#include <ctype.h>
66862fdf1rgrimes#include <string.h>
67862fdf1rgrimes#include "lp.h"
68862fdf1rgrimes#include "lp.local.h"
69862fdf1rgrimes#include "lpc.h"
70862fdf1rgrimes#include "extern.h"
71862fdf1rgrimes#include "pathnames.h"
72862fdf1rgrimes
731c055fbgad/*
741c055fbgad * Return values from kill_qtask().
751c055fbgad */
761c055fbgad#define KQT_LFERROR	-2
771c055fbgad#define KQT_KILLFAIL	-1
781c055fbgad#define KQT_NODAEMON	0
791c055fbgad#define KQT_KILLOK	1
801c055fbgad
816bfe568gadstatic char	*args2line(int argc, char **argv);
82d614283gadstatic int	 doarg(char *_job);
8309bba37kevlostatic int	 doselect(const struct dirent *_d);
841c055fbgadstatic int	 kill_qtask(const char *lf);
85716dc0adelphijstatic int	 sortq(const struct dirent **a, const struct dirent **b);
86d614283gadstatic int	 touch(struct jobqueue *_jq);
87d614283gadstatic void	 unlinkf(char *_name);
886bfe568gadstatic void	 upstat(struct printer *_pp, const char *_msg, int _notify);
89a89ca1egadstatic void	 wrapup_clean(int _laststatus);
90862fdf1rgrimes
91862fdf1rgrimes/*
92bf21e6bwollman * generic framework for commands which operate on all or a specified
93bf21e6bwollman * set of printers
94862fdf1rgrimes */
95a89ca1egadenum	qsel_val {			/* how a given ptr was selected */
96a89ca1egad	QSEL_UNKNOWN = -1,		/* ... not selected yet */
97a89ca1egad	QSEL_BYNAME = 0,		/* ... user specifed it by name */
98a89ca1egad	QSEL_ALL = 1			/* ... user wants "all" printers */
99a89ca1egad					/*     (with more to come)    */
100a89ca1egad};
101a89ca1egad
102a89ca1egadstatic enum qsel_val generic_qselect;	/* indicates how ptr was selected */
103a89ca1egadstatic int generic_initerr;		/* result of initrtn processing */
104d26b6e9gadstatic char *generic_cmdname;
1056bfe568gadstatic char *generic_msg;		/* if a -msg was specified */
106a89ca1egadstatic char *generic_nullarg;
107a89ca1egadstatic void (*generic_wrapup)(int _last_status);   /* perform rtn wrap-up */
108a89ca1egad
109862fdf1rgrimesvoid
1106bfe568gadgeneric(void (*specificrtn)(struct printer *_pp), int cmdopts,
111a89ca1egad    void (*initrtn)(int _argc, char *_argv[]), int argc, char *argv[])
112862fdf1rgrimes{
113a89ca1egad	int cmdstatus, more, targc;
114a89ca1egad	struct printer myprinter, *pp;
115d26b6e9gad	char **margv, **targv;
116862fdf1rgrimes
117862fdf1rgrimes	if (argc == 1) {
118075d723gad		/*
119075d723gad		 * Usage needs a special case for 'down': The user must
120075d723gad		 * either include `-msg', or only the first parameter
121075d723gad		 * that they give will be processed as a printer name.
122075d723gad		 */
1236bfe568gad		printf("usage: %s  {all | printer ...}", argv[0]);
124075d723gad		if (strcmp(argv[0], "down") == 0) {
125075d723gad			printf(" -msg [<text> ...]\n");
126075d723gad			printf("   or: down  {all | printer} [<text> ...]");
127075d723gad		} else if (cmdopts & LPC_MSGOPT)
1286bfe568gad			printf(" [-msg <text> ...]");
1296bfe568gad		printf("\n");
130bf21e6bwollman		return;
131bf21e6bwollman	}
132a89ca1egad
133d26b6e9gad	/* The first argument is the command name. */
134d26b6e9gad	generic_cmdname = *argv++;
135d26b6e9gad	argc--;
136d26b6e9gad
137a89ca1egad	/*
138a89ca1egad	 * The initialization routine for a command might set a generic
139a89ca1egad	 * "wrapup" routine, which should be called after processing all
140a89ca1egad	 * the printers in the command.  This might print summary info.
141a89ca1egad	 *
142a89ca1egad	 * Note that the initialization routine may also parse (and
143a89ca1egad	 * nullify) some of the parameters given on the command, leaving
144a89ca1egad	 * only the parameters which have to do with printer names.
145a89ca1egad	 */
146a89ca1egad	pp = &myprinter;
147a89ca1egad	generic_wrapup = NULL;
148a89ca1egad	generic_qselect = QSEL_UNKNOWN;
149a89ca1egad	cmdstatus = 0;
150a89ca1egad	/* this just needs to be a distinct value of type 'char *' */
151a89ca1egad	if (generic_nullarg == NULL)
152a89ca1egad		generic_nullarg = strdup("");
153a89ca1egad
1546bfe568gad	/*
1556bfe568gad	 * Some commands accept a -msg argument, which indicates that
1566bfe568gad	 * all remaining arguments should be combined into a string.
1576bfe568gad	 */
1586bfe568gad	generic_msg = NULL;
1596bfe568gad	if (cmdopts & LPC_MSGOPT) {
1606bfe568gad		targc = argc;
1616bfe568gad		targv = argv;
162d26b6e9gad		for (; targc > 0; targc--, targv++) {
1636bfe568gad			if (strcmp(*targv, "-msg") == 0) {
1646bfe568gad				argc -= targc;
1656bfe568gad				generic_msg = args2line(targc - 1, targv + 1);
1666bfe568gad				break;
1676bfe568gad			}
1686bfe568gad		}
1695fb190bgad		if (argc < 1) {
1705fb190bgad			printf("error: No printer name(s) specified before"
1715fb190bgad			    " '-msg'.\n");
1725fb190bgad			printf("usage: %s  {all | printer ...}",
1735fb190bgad			    generic_cmdname);
1745fb190bgad			printf(" [-msg <text> ...]\n");
1755fb190bgad			return;
1765fb190bgad		}
1776bfe568gad	}
1786bfe568gad
179a89ca1egad	/* call initialization routine, if there is one for this cmd */
180a89ca1egad	if (initrtn != NULL) {
181a89ca1egad		generic_initerr = 0;
182a89ca1egad		(*initrtn)(argc, argv);
183a89ca1egad		if (generic_initerr)
184a89ca1egad			return;
185d26b6e9gad		/*
186d26b6e9gad		 * The initrtn may have null'ed out some of the parameters.
187d26b6e9gad		 * Compact the parameter list to remove those nulls, and
188d26b6e9gad		 * correct the arg-count.
189d26b6e9gad		 */
190a89ca1egad		targc = argc;
191a89ca1egad		targv = argv;
192d26b6e9gad		margv = argv;
193d26b6e9gad		argc = 0;
194d26b6e9gad		for (; targc > 0; targc--, targv++) {
195d26b6e9gad			if (*targv != generic_nullarg) {
196d26b6e9gad				if (targv != margv)
197d26b6e9gad					*margv = *targv;
198d26b6e9gad				margv++;
199d26b6e9gad				argc++;
200d26b6e9gad			}
201a89ca1egad		}
202a89ca1egad	}
203a89ca1egad
204d26b6e9gad	if (argc == 1 && strcmp(*argv, "all") == 0) {
205a89ca1egad		generic_qselect = QSEL_ALL;
206d614283gad		more = firstprinter(pp, &cmdstatus);
207d614283gad		if (cmdstatus)
208bf21e6bwollman			goto looperr;
209bf21e6bwollman		while (more) {
210d614283gad			(*specificrtn)(pp);
211bf21e6bwollman			do {
212d614283gad				more = nextprinter(pp, &cmdstatus);
213bf21e6bwollmanlooperr:
214d614283gad				switch (cmdstatus) {
215bf21e6bwollman				case PCAPERR_TCOPEN:
216bf21e6bwollman					printf("warning: %s: unresolved "
217bf21e6bwollman					       "tc= reference(s) ",
218bf21e6bwollman					       pp->printer);
219bf21e6bwollman				case PCAPERR_SUCCESS:
220bf21e6bwollman					break;
221bf21e6bwollman				default:
22275f5203gad					fatal(pp, "%s", pcaperr(cmdstatus));
223bf21e6bwollman				}
224d614283gad			} while (more && cmdstatus);
225862fdf1rgrimes		}
226a89ca1egad		goto wrapup;
227862fdf1rgrimes	}
228a89ca1egad
229a89ca1egad	generic_qselect = QSEL_BYNAME;		/* specifically-named ptrs */
230d26b6e9gad	for (; argc > 0; argc--, argv++) {
231bf21e6bwollman		init_printer(pp);
232d614283gad		cmdstatus = getprintcap(*argv, pp);
233d614283gad		switch (cmdstatus) {
234bf21e6bwollman		default:
23575f5203gad			fatal(pp, "%s", pcaperr(cmdstatus));
236bf21e6bwollman		case PCAPERR_NOTFOUND:
237bf21e6bwollman			printf("unknown printer %s\n", *argv);
238862fdf1rgrimes			continue;
239bf21e6bwollman		case PCAPERR_TCOPEN:
240bf21e6bwollman			printf("warning: %s: unresolved tc= reference(s)\n",
241bf21e6bwollman			       *argv);
242bf21e6bwollman			break;
243bf21e6bwollman		case PCAPERR_SUCCESS:
244bf21e6bwollman			break;
245bf21e6bwollman		}
246d614283gad		(*specificrtn)(pp);
247862fdf1rgrimes	}
248a89ca1egad
249a89ca1egadwrapup:
250a89ca1egad	if (generic_wrapup) {
251a89ca1egad		(*generic_wrapup)(cmdstatus);
252a89ca1egad	}
253f9c62a9gad	free_printer(pp);
2546bfe568gad	if (generic_msg)
2556bfe568gad		free(generic_msg);
2566bfe568gad}
257a89ca1egad
2586bfe568gad/*
2596bfe568gad * Convert an argv-array of character strings into a single string.
2606bfe568gad */
2616bfe568gadstatic char *
2626bfe568gadargs2line(int argc, char **argv)
2636bfe568gad{
2646bfe568gad	char *cp1, *cend;
2656bfe568gad	const char *cp2;
2666bfe568gad	char buf[1024];
2676bfe568gad
2686bfe568gad	if (argc <= 0)
2696bfe568gad		return strdup("\n");
2706bfe568gad
2716bfe568gad	cp1 = buf;
2726bfe568gad	cend = buf + sizeof(buf) - 1;		/* save room for '\0' */
2736bfe568gad	while (--argc >= 0) {
2746bfe568gad		cp2 = *argv++;
2756bfe568gad		while ((cp1 < cend) && (*cp1++ = *cp2++))
2766bfe568gad			;
2776bfe568gad		cp1[-1] = ' ';
2786bfe568gad	}
2796bfe568gad	cp1[-1] = '\n';
2806bfe568gad	*cp1 = '\0';
2816bfe568gad	return strdup(buf);
282862fdf1rgrimes}
283862fdf1rgrimes
284bf21e6bwollman/*
2851c055fbgad * Kill the current daemon, to stop printing of the active job.
2861c055fbgad */
2871c055fbgadstatic int
2881c055fbgadkill_qtask(const char *lf)
2891c055fbgad{
2901c055fbgad	FILE *fp;
2911c055fbgad	pid_t pid;
2921c055fbgad	int errsav, killres, lockres, res;
2931c055fbgad
2944bc19b0eadler	PRIV_START
2951c055fbgad	fp = fopen(lf, "r");
2961c055fbgad	errsav = errno;
2974bc19b0eadler	PRIV_END
2981c055fbgad	res = KQT_NODAEMON;
2991c055fbgad	if (fp == NULL) {
3001c055fbgad		/*
3011c055fbgad		 * If there is no lock file, then there is no daemon to
3021c055fbgad		 * kill.  Any other error return means there is some
3031c055fbgad		 * kind of problem with the lock file.
3041c055fbgad		 */
3051c055fbgad		if (errsav != ENOENT)
3061c055fbgad			res = KQT_LFERROR;
3071c055fbgad		goto killdone;
3081c055fbgad	}
3091c055fbgad
3101c055fbgad	/* If the lock file is empty, then there is no daemon to kill */
311c80a45cbapt	if (get_line(fp) == 0)
3121c055fbgad		goto killdone;
3131c055fbgad
3141c055fbgad	/*
3151c055fbgad	 * If the file can be locked without blocking, then there
3161c055fbgad	 * no daemon to kill, or we should not try to kill it.
3171c055fbgad	 *
3181c055fbgad	 * XXX - not sure I understand the reasoning behind this...
3191c055fbgad	 */
3201c055fbgad	lockres = flock(fileno(fp), LOCK_SH|LOCK_NB);
3211c055fbgad	(void) fclose(fp);
3221c055fbgad	if (lockres == 0)
3231c055fbgad		goto killdone;
3241c055fbgad
3251c055fbgad	pid = atoi(line);
3261c055fbgad	if (pid < 0) {
3271c055fbgad		/*
3281c055fbgad		 * If we got a negative pid, then the contents of the
3291c055fbgad		 * lock file is not valid.
3301c055fbgad		 */
3311c055fbgad		res = KQT_LFERROR;
3321c055fbgad		goto killdone;
3331c055fbgad	}
3341c055fbgad
3354bc19b0eadler	PRIV_END
3361c055fbgad	killres = kill(pid, SIGTERM);
3371c055fbgad	errsav = errno;
3384bc19b0eadler	PRIV_END
3391c055fbgad	if (killres == 0) {
3401c055fbgad		res = KQT_KILLOK;
3411c055fbgad		printf("\tdaemon (pid %d) killed\n", pid);
3421c055fbgad	} else if (errno == ESRCH) {
3431c055fbgad		res = KQT_NODAEMON;
3441c055fbgad	} else {
3451c055fbgad		res = KQT_KILLFAIL;
3461c055fbgad		printf("\tWarning: daemon (pid %d) not killed:\n", pid);
3471c055fbgad		printf("\t    %s\n", strerror(errsav));
3481c055fbgad	}
3491c055fbgad
3501c055fbgadkilldone:
3511c055fbgad	switch (res) {
3521c055fbgad	case KQT_LFERROR:
3531c055fbgad		printf("\tcannot open lock file: %s\n",
3541c055fbgad		    strerror(errsav));
3551c055fbgad		break;
3561c055fbgad	case KQT_NODAEMON:
3571c055fbgad		printf("\tno daemon to abort\n");
3581c055fbgad		break;
3591c055fbgad	case KQT_KILLFAIL:
3601c055fbgad	case KQT_KILLOK:
3611c055fbgad		/* These two already printed messages to the user. */
3621c055fbgad		break;
3631c055fbgad	default:
3641c055fbgad		printf("\t<internal error in kill_qtask>\n");
3651c055fbgad		break;
3661c055fbgad	}
3671c055fbgad
3681c055fbgad	return (res);
3691c055fbgad}
3701c055fbgad
3711c055fbgad/*
372862fdf1rgrimes * Write a message into the status file.
373862fdf1rgrimes */
374862fdf1rgrimesstatic void
3756bfe568gadupstat(struct printer *pp, const char *msg, int notifyuser)
376862fdf1rgrimes{
3776bfe568gad	int fd;
378b1debf9imp	char statfile[MAXPATHLEN];
379862fdf1rgrimes
380bf21e6bwollman	status_file_name(pp, statfile, sizeof statfile);
381862fdf1rgrimes	umask(0);
3824bc19b0eadler	PRIV_START
383bf21e6bwollman	fd = open(statfile, O_WRONLY|O_CREAT|O_EXLOCK, STAT_FILE_MODE);
3844bc19b0eadler	PRIV_END
385bf21e6bwollman	if (fd < 0) {
386bf21e6bwollman		printf("\tcannot create status file: %s\n", strerror(errno));
387862fdf1rgrimes		return;
388862fdf1rgrimes	}
389862fdf1rgrimes	(void) ftruncate(fd, 0);
39009bba37kevlo	if (msg == NULL)
391862fdf1rgrimes		(void) write(fd, "\n", 1);
392862fdf1rgrimes	else
393862fdf1rgrimes		(void) write(fd, msg, strlen(msg));
394862fdf1rgrimes	(void) close(fd);
3956bfe568gad	if (notifyuser) {
3966bfe568gad		if ((msg == (char *)NULL) || (strcmp(msg, "\n") == 0))
3976bfe568gad			printf("\tstatus message is now set to nothing.\n");
3986bfe568gad		else
3996bfe568gad			printf("\tstatus message is now: %s", msg);
4006bfe568gad	}
401862fdf1rgrimes}
402862fdf1rgrimes
403a89ca1egad/*
4041c055fbgad * kill an existing daemon and disable printing.
4051c055fbgad */
4061c055fbgadvoid
4071c055fbgadabort_q(struct printer *pp)
4081c055fbgad{
4091c055fbgad	int killres, setres;
4101c055fbgad	char lf[MAXPATHLEN];
4111c055fbgad
4121c055fbgad	lock_file_name(pp, lf, sizeof lf);
4131c055fbgad	printf("%s:\n", pp->printer);
4141c055fbgad
4151c055fbgad	/*
4161c055fbgad	 * Turn on the owner execute bit of the lock file to disable printing.
4171c055fbgad	 */
4181c055fbgad	setres = set_qstate(SQS_STOPP, lf);
4191c055fbgad
4201c055fbgad	/*
4211c055fbgad	 * If set_qstate found that there already was a lock file, then
4221c055fbgad	 * call a routine which will read that lock file and kill the
4231c055fbgad	 * lpd-process which is listed in that lock file.  If the lock
4241c055fbgad	 * file did not exist, then either there is no daemon running
4251c055fbgad	 * for this queue, or there is one running but *it* could not
4261c055fbgad	 * write a lock file (which means we can not determine the
4271c055fbgad	 * process id of that lpd-process).
4281c055fbgad	 */
4291c055fbgad	switch (setres) {
4301c055fbgad	case SQS_CHGOK:
4311c055fbgad	case SQS_CHGFAIL:
4321c055fbgad		/* Kill the process */
4331c055fbgad		killres = kill_qtask(lf);
4341c055fbgad		break;
4351c055fbgad	case SQS_CREOK:
4361c055fbgad	case SQS_CREFAIL:
4371c055fbgad		printf("\tno daemon to abort\n");
4381c055fbgad		break;
4391c055fbgad	case SQS_STATFAIL:
4401c055fbgad		printf("\tassuming no daemon to abort\n");
4411c055fbgad		break;
4421c055fbgad	default:
4431c055fbgad		printf("\t<unexpected result (%d) from set_qstate>\n",
4441c055fbgad		    setres);
4451c055fbgad		break;
4461c055fbgad	}
4471c055fbgad
4486bfe568gad	if (setres >= 0)
4496bfe568gad		upstat(pp, "printing disabled\n", 0);
4501c055fbgad}
4511c055fbgad
4521c055fbgad/*
453a89ca1egad * "global" variables for all the routines related to 'clean' and 'tclean'
454a89ca1egad */
455a89ca1egadstatic time_t	 cln_now;		/* current time */
456a89ca1egadstatic double	 cln_minage;		/* minimum age before file is removed */
457a89ca1egadstatic long	 cln_sizecnt;		/* amount of space freed up */
458a89ca1egadstatic int 	 cln_debug;		/* print extra debugging msgs */
459a89ca1egadstatic int	 cln_filecnt;		/* number of files destroyed */
460a89ca1egadstatic int	 cln_foundcore;		/* found a core file! */
461a89ca1egadstatic int	 cln_queuecnt;		/* number of queues checked */
462a89ca1egadstatic int 	 cln_testonly;		/* remove-files vs just-print-info */
463a89ca1egad
464862fdf1rgrimesstatic int
46509bba37kevlodoselect(const struct dirent *d)
466862fdf1rgrimes{
467862fdf1rgrimes	int c = d->d_name[0];
468862fdf1rgrimes
469f06e629gad	if ((c == 'c' || c == 'd' || c == 'r' || c == 't') &&
470f06e629gad	    d->d_name[1] == 'f')
471a89ca1egad		return 1;
472a89ca1egad	if (c == 'c') {
473a89ca1egad		if (!strcmp(d->d_name, "core"))
474a89ca1egad			cln_foundcore = 1;
475a89ca1egad	}
476a89ca1egad	if (c == 'e') {
477a89ca1egad		if (!strncmp(d->d_name, "errs.", 5))
478a89ca1egad			return 1;
479a89ca1egad	}
480a89ca1egad	return 0;
481862fdf1rgrimes}
482862fdf1rgrimes
483862fdf1rgrimes/*
4841053a31gad * Comparison routine that clean_q() uses for scandir.
4851053a31gad *
4861053a31gad * The purpose of this sort is to have all `df' files end up immediately
487f06e629gad * after the matching `cf' file.  For files matching `cf', `df', `rf', or
488f06e629gad * `tf', it sorts by job number and machine, then by `cf', `df', `rf', or
489f06e629gad * `tf', and then by the sequence letter (which is A-Z, or a-z).    This
490f06e629gad * routine may also see filenames which do not start with `cf', `df', `rf',
491f06e629gad * or `tf' (such as `errs.*'), and those are simply sorted by the full
492f06e629gad * filename.
493f06e629gad *
494f06e629gad * XXX
495f06e629gad *   This assumes that all control files start with `cfA*', and it turns
496f06e629gad *   out there are a few implementations of lpr which will create `cfB*'
497f06e629gad *   filenames (they will have datafile names which start with `dfB*').
498862fdf1rgrimes */
499862fdf1rgrimesstatic int
500716dc0adelphijsortq(const struct dirent **a, const struct dirent **b)
501862fdf1rgrimes{
5021053a31gad	const int a_lt_b = -1, a_gt_b = 1, cat_other = 10;
5031053a31gad	const char *fname_a, *fname_b, *jnum_a, *jnum_b;
5041053a31gad	int cat_a, cat_b, ch, res, seq_a, seq_b;
5051053a31gad
506716dc0adelphij	fname_a = (*a)->d_name;
507716dc0adelphij	fname_b = (*b)->d_name;
5081053a31gad
5091053a31gad	/*
510415a3a9uqs	 * First separate filenames into categories.  Categories are
511f06e629gad	 * legitimate `cf', `df', `rf' & `tf' filenames, and "other" - in
512f06e629gad	 * that order.  It is critical that the mapping be exactly the
513f06e629gad	 * same for 'a' vs 'b', so define a macro for the job.
5141053a31gad	 *
5151053a31gad	 * [aside: the standard `cf' file has the jobnumber start in
5161053a31gad	 * position 4, but some implementations have that as an extra
5171053a31gad	 * file-sequence letter, and start the job number in position 5.]
5181053a31gad	 */
5191053a31gad#define MAP_TO_CAT(fname_X,cat_X,jnum_X,seq_X) do { \
5201053a31gad	cat_X = cat_other;    \
5211053a31gad	ch = *(fname_X + 2);  \
5221053a31gad	jnum_X = fname_X + 3; \
52328ccdf6gad	seq_X = 0;            \
5241053a31gad	if ((*(fname_X + 1) == 'f') && (isalpha(ch))) { \
5251053a31gad		seq_X = ch; \
5261053a31gad		if (*fname_X == 'c') \
5271053a31gad			cat_X = 1; \
5281053a31gad		else if (*fname_X == 'd') \
5291053a31gad			cat_X = 2; \
530f06e629gad		else if (*fname_X == 'r') \
5311053a31gad			cat_X = 3; \
532f06e629gad		else if (*fname_X == 't') \
533f06e629gad			cat_X = 4; \
5341053a31gad		if (cat_X != cat_other) { \
5351053a31gad			ch = *jnum_X; \
5361053a31gad			if (!isdigit(ch)) { \
5371053a31gad				if (isalpha(ch)) { \
5381053a31gad					jnum_X++; \
5391053a31gad					ch = *jnum_X; \
5401053a31gad					seq_X = (seq_X << 8) + ch; \
5411053a31gad				} \
5421053a31gad				if (!isdigit(ch)) \
5431053a31gad					cat_X = cat_other; \
5441053a31gad			} \
5451053a31gad		} \
5461053a31gad	} \
5471053a31gad} while (0)
5481053a31gad
5491053a31gad	MAP_TO_CAT(fname_a, cat_a, jnum_a, seq_a);
5501053a31gad	MAP_TO_CAT(fname_b, cat_b, jnum_b, seq_b);
5511053a31gad
5521053a31gad#undef MAP_TO_CAT
5531053a31gad
5541053a31gad	/* First handle all cases which have "other" files */
5551053a31gad	if ((cat_a >= cat_other) || (cat_b >= cat_other)) {
5561053a31gad		/* for two "other" files, just compare the full name */
5571053a31gad		if (cat_a == cat_b)
5581053a31gad			res = strcmp(fname_a, fname_b);
5591053a31gad		else if (cat_a < cat_b)
5601053a31gad			res = a_lt_b;
5611053a31gad		else
5621053a31gad			res = a_gt_b;
5631053a31gad		goto have_res;
5641053a31gad	}
5651053a31gad
5661053a31gad	/*
567f06e629gad	 * At this point, we know both files are legitimate `cf', `df', `rf',
5681053a31gad	 * or `tf' files.  Compare them by job-number and machine name.
5691053a31gad	 */
5701053a31gad	res = strcmp(jnum_a, jnum_b);
5711053a31gad	if (res != 0)
5721053a31gad		goto have_res;
573