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