1/*-
2 * Copyright (c) 2005-2006 The FreeBSD Project
3 * All rights reserved.
4 *
5 * Author: Victor Cruceru <soc-victor@freebsd.org>
6 *
7 * Redistribution of this software and documentation and use in source and
8 * binary forms, with or without modification, are permitted provided that
9 * the following conditions are met:
10 *
11 * 1. Redistributions of source code or documentation must retain the above
12 *    copyright 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
30/*
31 * Host Resources MIB implementation for SNMPd: instrumentation for
32 * hrPrinterTable
33 */
34
35#include <sys/param.h>
36#include <sys/stat.h>
37
38#include <assert.h>
39#include <err.h>
40#include <errno.h>
41#include <paths.h>
42#include <stdlib.h>
43#include <string.h>
44#include <syslog.h>
45#include <unistd.h>
46
47#include "hostres_snmp.h"
48#include "hostres_oid.h"
49#include "hostres_tree.h"
50
51#include <sys/dirent.h>
52#include "lp.h"
53
54/* Constants */
55static const struct asn_oid OIDX_hrDevicePrinter_c = OIDX_hrDevicePrinter;
56
57enum PrinterStatus {
58	PS_OTHER	= 1,
59	PS_UNKNOWN	= 2,
60	PS_IDLE		= 3,
61	PS_PRINTING	= 4,
62	PS_WARMUP	= 5
63};
64
65/*
66 * This structure is used to hold a SNMP table entry
67 * for HOST-RESOURCES-MIB's hrPrinterTable.
68 */
69struct printer_entry {
70	int32_t		index;
71	int32_t		status;  /* values from PrinterStatus enum above */
72	u_char		detectedErrorState[2];
73	TAILQ_ENTRY(printer_entry) link;
74#define	HR_PRINTER_FOUND		0x001
75	uint32_t	flags;
76
77};
78TAILQ_HEAD(printer_tbl, printer_entry);
79
80/* the hrPrinterTable */
81static struct printer_tbl printer_tbl = TAILQ_HEAD_INITIALIZER(printer_tbl);
82
83/* last (agent) tick when hrPrinterTable was updated */
84static uint64_t printer_tick;
85
86/**
87 * Create entry into the printer table.
88 */
89static struct printer_entry *
90printer_entry_create(const struct device_entry *devEntry)
91{
92	struct printer_entry *entry = NULL;
93
94	assert(devEntry != NULL);
95	if (devEntry == NULL)
96		return (NULL);
97
98	if ((entry = malloc(sizeof(*entry))) == NULL) {
99		syslog(LOG_WARNING, "hrPrinterTable: %s: %m", __func__);
100		return (NULL);
101	}
102	memset(entry, 0, sizeof(*entry));
103	entry->index = devEntry->index;
104	INSERT_OBJECT_INT(entry, &printer_tbl);
105	return (entry);
106}
107
108/**
109 * Delete entry from the printer table.
110 */
111static void
112printer_entry_delete(struct printer_entry *entry)
113{
114
115	assert(entry != NULL);
116	if (entry == NULL)
117		return;
118
119	TAILQ_REMOVE(&printer_tbl, entry, link);
120	free(entry);
121}
122
123/**
124 * Find a printer by its index
125 */
126static struct printer_entry *
127printer_find_by_index(int32_t idx)
128{
129	struct printer_entry *entry;
130
131	TAILQ_FOREACH(entry, &printer_tbl, link)
132		if (entry->index == idx)
133			return (entry);
134
135	return (NULL);
136}
137
138/**
139 * Get the status of a printer
140 */
141static enum PrinterStatus
142get_printer_status(const struct printer *pp)
143{
144	char statfile[MAXPATHLEN];
145	char lockfile[MAXPATHLEN];
146	char fline[128];
147	int fd;
148	FILE *f = NULL;
149	enum PrinterStatus ps = PS_UNKNOWN;
150
151	if (pp->lock_file[0] == '/')
152		strlcpy(lockfile, pp->lock_file, sizeof(lockfile));
153	else
154		snprintf(lockfile, sizeof(lockfile), "%s/%s",
155		    pp->spool_dir, pp->lock_file);
156
157	fd = open(lockfile, O_RDONLY);
158	if (fd < 0 || flock(fd, LOCK_SH | LOCK_NB) == 0) {
159		ps = PS_IDLE;
160		goto LABEL_DONE;
161	}
162
163	if (pp->status_file[0] == '/')
164		strlcpy(statfile, pp->status_file, sizeof(statfile));
165	else
166		snprintf(statfile, sizeof(statfile), "%s/%s",
167		    pp->spool_dir, pp->status_file);
168
169	f = fopen(statfile, "r");
170	if (f == NULL) {
171		syslog(LOG_ERR, "cannot open status file: %s", strerror(errno));
172		ps = PS_UNKNOWN;
173		goto LABEL_DONE;
174	}
175
176	memset(&fline[0], '\0', sizeof(fline));
177	if (fgets(fline, sizeof(fline) -1, f) == NULL) {
178		ps = PS_UNKNOWN;
179		goto LABEL_DONE;
180	}
181
182	if (strstr(fline, "is ready and printing") != NULL) {
183		ps = PS_PRINTING;
184		goto LABEL_DONE;
185	}
186
187	if (strstr(fline, "to become ready (offline?)") != NULL) {
188		ps = PS_OTHER;
189		goto LABEL_DONE;
190	}
191
192LABEL_DONE:
193	if (fd >= 0)
194		(void)close(fd);	/* unlocks as well */
195
196	if (f != NULL)
197		(void)fclose(f);
198
199	return (ps);
200}
201
202/**
203 * Called for each printer found in /etc/printcap.
204 */
205static void
206handle_printer(struct printer *pp)
207{
208	struct device_entry *dev_entry;
209	struct printer_entry *printer_entry;
210	char dev_only[128];
211	struct stat sb;
212
213	if (pp->remote_host != NULL) {
214		HRDBG("skipped %s -- remote", pp->printer);
215		return;
216	}
217
218	if (strncmp(pp->lp, _PATH_DEV, strlen(_PATH_DEV)) != 0) {
219		HRDBG("skipped %s [device %s] -- remote", pp->printer, pp->lp);
220		return;
221	}
222
223	memset(dev_only, '\0', sizeof(dev_only));
224	snprintf(dev_only, sizeof(dev_only), "%s", pp->lp + strlen(_PATH_DEV));
225
226	HRDBG("printer %s has device %s", pp->printer, dev_only);
227
228	if (stat(pp->lp, &sb) < 0) {
229		if (errno == ENOENT) {
230			HRDBG("skipped %s -- device %s missing",
231			    pp->printer, pp->lp);
232			return;
233		}
234	}
235
236	if ((dev_entry = device_find_by_name(dev_only)) == NULL) {
237		HRDBG("%s not in hrDeviceTable", pp->lp);
238		return;
239	}
240	HRDBG("%s found in hrDeviceTable", pp->lp);
241	dev_entry->type = &OIDX_hrDevicePrinter_c;
242
243	dev_entry->flags |= HR_DEVICE_IMMUTABLE;
244
245	/* Then check hrPrinterTable for this device */
246	if ((printer_entry = printer_find_by_index(dev_entry->index)) == NULL &&
247	    (printer_entry = printer_entry_create(dev_entry)) == NULL)
248		return;
249
250	printer_entry->flags |= HR_PRINTER_FOUND;
251	printer_entry->status = get_printer_status(pp);
252	memset(printer_entry->detectedErrorState, 0,
253	    sizeof(printer_entry->detectedErrorState));
254}
255
256static void
257hrPrinter_get_OS_entries(void)
258{
259	int  status, more;
260	struct printer myprinter, *pp = &myprinter;
261
262	init_printer(pp);
263	HRDBG("---->Getting printers .....");
264	more = firstprinter(pp, &status);
265	if (status)
266		goto errloop;
267
268	while (more) {
269		do {
270			HRDBG("---->Got printer %s", pp->printer);
271
272			handle_printer(pp);
273			more = nextprinter(pp, &status);
274errloop:
275			if (status)
276				syslog(LOG_WARNING,
277				    "hrPrinterTable: printcap entry for %s "
278				    "has errors, skipping",
279				    pp->printer ? pp->printer : "<noname?>");
280		} while (more && status);
281	}
282
283	lastprinter();
284	printer_tick = this_tick;
285}
286
287/**
288 * Init the things for hrPrinterTable
289 */
290void
291init_printer_tbl(void)
292{
293
294	hrPrinter_get_OS_entries();
295}
296
297/**
298 * Finalization routine for hrPrinterTable
299 * It destroys the lists and frees any allocated heap memory
300 */
301void
302fini_printer_tbl(void)
303{
304	struct printer_entry *n1;
305
306	while ((n1 = TAILQ_FIRST(&printer_tbl)) != NULL) {
307		TAILQ_REMOVE(&printer_tbl, n1, link);
308		free(n1);
309	}
310}
311
312/**
313 * Refresh the printer table if needed.
314 */
315void
316refresh_printer_tbl(void)
317{
318	struct printer_entry *entry;
319	struct printer_entry *entry_tmp;
320
321	if (this_tick <= printer_tick) {
322		HRDBG("no refresh needed");
323		return;
324	}
325
326	/* mark each entry as missing */
327	TAILQ_FOREACH(entry, &printer_tbl, link)
328		entry->flags &= ~HR_PRINTER_FOUND;
329
330	hrPrinter_get_OS_entries();
331
332	/*
333	 * Purge items that disappeared
334	 */
335	entry = TAILQ_FIRST(&printer_tbl);
336	while (entry != NULL) {
337		entry_tmp = TAILQ_NEXT(entry, link);
338		if (!(entry->flags & HR_PRINTER_FOUND))
339			printer_entry_delete(entry);
340		entry = entry_tmp;
341	}
342
343	printer_tick = this_tick;
344
345	HRDBG("refresh DONE ");
346}
347
348int
349op_hrPrinterTable(struct snmp_context *ctx __unused, struct snmp_value *value,
350    u_int sub, u_int iidx __unused, enum snmp_op curr_op)
351{
352	struct printer_entry *entry;
353
354	refresh_printer_tbl();
355
356	switch (curr_op) {
357
358	case SNMP_OP_GETNEXT:
359		if ((entry = NEXT_OBJECT_INT(&printer_tbl, &value->var,
360		    sub)) == NULL)
361			return (SNMP_ERR_NOSUCHNAME);
362		value->var.len = sub + 1;
363		value->var.subs[sub] = entry->index;
364		goto get;
365
366	case SNMP_OP_GET:
367		if ((entry = FIND_OBJECT_INT(&printer_tbl, &value->var,
368		    sub)) == NULL)
369			return (SNMP_ERR_NOSUCHNAME);
370		goto get;
371
372	case SNMP_OP_SET:
373		if ((entry = FIND_OBJECT_INT(&printer_tbl, &value->var,
374		    sub)) == NULL)
375			return (SNMP_ERR_NO_CREATION);
376		return (SNMP_ERR_NOT_WRITEABLE);
377
378	case SNMP_OP_ROLLBACK:
379	case SNMP_OP_COMMIT:
380		abort();
381	}
382	abort();
383
384  get:
385	switch (value->var.subs[sub - 1]) {
386
387	case LEAF_hrPrinterStatus:
388		value->v.integer = entry->status;
389		return (SNMP_ERR_NOERROR);
390
391	case LEAF_hrPrinterDetectedErrorState:
392		return (string_get(value, entry->detectedErrorState,
393		    sizeof(entry->detectedErrorState)));
394	}
395	abort();
396}
397