1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * ------+---------+---------+---------+---------+---------+---------+---------*
5 * Copyright (c) 2002   - Garance Alistair Drosehn <gad@FreeBSD.org>.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *   1. Redistributions of source code must retain the above copyright
12 *      notice, this list of conditions and the following disclaimer.
13 *   2. Redistributions in binary form must reproduce the above copyright
14 *      notice, this list of conditions and the following disclaimer in the
15 *      documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * The views and conclusions contained in the software and documentation
30 * are those of the authors and should not be interpreted as representing
31 * official policies, either expressed or implied, of the FreeBSD Project
32 * or FreeBSD, Inc.
33 *
34 * ------+---------+---------+---------+---------+---------+---------+---------*
35 */
36
37#include "lp.cdefs.h"		/* A cross-platform version of <sys/cdefs.h> */
38/*
39 * movejobs.c - The lpc commands which move jobs around.
40 */
41
42#include <sys/file.h>
43#include <sys/param.h>
44#include <sys/queue.h>
45#include <sys/time.h>
46#include <sys/stat.h>
47
48#include <ctype.h>
49#include <dirent.h>	/* just for MAXNAMLEN, for job_cfname in lp.h! */
50#include <err.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <string.h>
54#include <unistd.h>
55#include "lp.h"
56#include "lpc.h"
57#include "matchjobs.h"
58#include "extern.h"
59
60/* Values for origcmd in tqbq_common() */
61#define IS_TOPQ		1
62#define IS_BOTQ 	2
63
64static int	 process_jobs(int _argc, char *_argv[], process_jqe
65		    _process_rtn, void *myinfo);
66static process_jqe touch_jqe;
67static void 	 tqbq_common(int _argc, char *_argv[], int _origcmd);
68
69/*
70 * isdigit is defined to work on an 'int', in the range 0 to 255, plus EOF.
71 * Define a wrapper which can take 'char', either signed or unsigned.
72 */
73#define isdigitch(Anychar)    isdigit(((int) Anychar) & 255)
74
75struct touchjqe_info {			/* for topq/bottomq */
76	time_t	 newtime;
77};
78
79static int	 nitems;
80static struct jobqueue **queue;
81
82/*
83 * Process all the jobs, as specified by the user.
84 */
85static int
86process_jobs(int argc, char *argv[], process_jqe process_rtn, void *myinfo)
87{
88	struct jobspec_hdr jobs_wanted;
89	int i, matchcnt, pjres;
90
91	STAILQ_INIT(&jobs_wanted);
92	for (i = 0; i < argc; i++) {
93		pjres = parse_jobspec(argv[i], &jobs_wanted);
94		if (pjres == 0) {
95			printf("\tinvalid job specifier: %s\n", argv[i]);
96			continue;
97		}
98	}
99	matchcnt = scanq_jobspec(nitems, queue, SCQ_JSORDER, &jobs_wanted,
100	    process_rtn, myinfo);
101
102	free_jobspec(&jobs_wanted);
103	return (matchcnt);
104}
105
106/*
107 * Reposition the job by changing the modification time of the
108 * control file.
109 */
110static int
111touch_jqe(void *myinfo, struct jobqueue *jq, struct jobspec *jspec)
112{
113	struct timeval tvp[2];
114	struct touchjqe_info *touch_info;
115	int ret;
116
117	/*
118	 * If the entire queue has been scanned for the current jobspec,
119	 * then let the user know if there were no jobs matched by that
120	 * specification.
121	 */
122	if (jq == NULL) {
123		if (jspec->matchcnt == 0) {
124			format_jobspec(jspec, FMTJS_VERBOSE);
125			if (jspec->pluralfmt)
126				printf("\tjobs %s are not in the queue\n",
127				    jspec->fmtoutput);
128			else
129				printf("\tjob %s is not in the queue\n",
130				    jspec->fmtoutput);
131		}
132		return (1);
133	}
134
135	/*
136	 * Do a little juggling with "matched" vs "processed", so a single
137	 * job can be matched by multiple specifications, and yet it will
138	 * be moved only once.  This is so, eg, 'topq lp 7 7' will not
139	 * complain "job 7 is not in queue" for the second specification.
140	 */
141	jq->job_matched = 0;
142	if (jq->job_processed) {
143		printf("\tmoved %s earlier\n", jq->job_cfname);
144		return (1);
145	}
146	jq->job_processed = 1;
147
148	touch_info = myinfo;
149	tvp[0].tv_sec = tvp[1].tv_sec = ++touch_info->newtime;
150	tvp[0].tv_usec = tvp[1].tv_usec = 0;
151	PRIV_START
152	ret = utimes(jq->job_cfname, tvp);
153	PRIV_END
154
155	if (ret == 0) {
156		if (jspec->matcheduser)
157			printf("\tmoved %s  (user %s)\n", jq->job_cfname,
158			    jspec->matcheduser);
159		else
160			printf("\tmoved %s\n", jq->job_cfname);
161	}
162	return (ret);
163}
164
165/*
166 * Put the specified jobs at the bottom of printer queue.
167 */
168void
169bottomq_cmd(int argc, char *argv[])
170{
171
172	if (argc < 3) {
173		printf("usage: bottomq printer [jobspec ...]\n");
174		return;
175	}
176	--argc;			/* First argv was the command name */
177	++argv;
178
179	tqbq_common(argc, argv, IS_BOTQ);
180}
181
182/*
183 * Put the specified jobs at the top of printer queue.
184 */
185void
186topq_cmd(int argc, char *argv[])
187{
188
189	if (argc < 3) {
190		printf("usage: topq printer [jobspec ...]\n");
191		return;
192	}
193	--argc;			/* First argv was the command name */
194	++argv;
195
196	tqbq_common(argc, argv, IS_TOPQ);
197}
198
199/*
200 * Processing in common between topq and bottomq commands.
201 */
202void
203tqbq_common(int argc, char *argv[], int origcmd)
204{
205	struct printer myprinter, *pp;
206	struct touchjqe_info touch_info;
207	int i, movecnt, setres;
208
209	pp = setup_myprinter(*argv, &myprinter, SUMP_CHDIR_SD);
210	if (pp == NULL)
211		return;
212	--argc;			/* Second argv was the printer name */
213	++argv;
214
215	nitems = getq(pp, &queue);
216	if (nitems == 0) {
217		printf("\tthere are no jobs in the queue\n");
218		free_printer(pp);
219		return;
220	}
221
222	/*
223	 * The only real difference between topq and bottomq is the
224	 * initial value used for newtime.
225	 */
226	switch (origcmd) {
227	case IS_BOTQ:
228		/*
229		 * When moving jobs to the bottom of the queue, pick a
230		 * starting value which is one second after the last job
231		 * in the queue.
232		*/
233		touch_info.newtime = queue[nitems - 1]->job_time + 1;
234		break;
235	case IS_TOPQ:
236		/*
237		 * When moving jobs to the top of the queue, the greatest
238		 * number of jobs which could be moved is all the jobs
239		 * that are in the queue.  Pick a starting value which
240		 * leaves plenty of room for all existing jobs.
241		 */
242		touch_info.newtime = queue[0]->job_time - nitems - 5;
243		break;
244	default:
245		printf("\ninternal error in topq/bottomq processing.\n");
246		return;
247	}
248
249	movecnt = process_jobs(argc, argv, touch_jqe, &touch_info);
250
251	/*
252	 * If any jobs were moved, then chmod the lock file to notify any
253	 * active process for this queue that the queue has changed, so
254	 * it will rescan the queue to find out the new job order.
255	 */
256	if (movecnt == 0)
257		printf("\tqueue order unchanged\n");
258	else {
259		setres = set_qstate(SQS_QCHANGED, pp->lock_file);
260		if (setres < 0)
261			printf("\t* queue order changed for %s, but the\n"
262			    "\t* attempt to set_qstate() failed [%d]!\n",
263			    pp->printer, setres);
264	}
265
266	for (i = 0; i < nitems; i++)
267		free(queue[i]);
268	free(queue);
269	free_printer(pp);
270}
271
272