diff -up system-config-printer-1.1.10/aclocal.m4.525e996 system-config-printer-1.1.10/aclocal.m4 --- system-config-printer-1.1.10/aclocal.m4.525e996 2009-07-22 13:54:02.000000000 +0100 +++ system-config-printer-1.1.10/aclocal.m4 2009-07-26 19:01:45.683356020 +0100 @@ -1867,6 +1867,162 @@ AC_DEFUN([AM_NLS], AC_SUBST(USE_NLS) ]) +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# +# Copyright © 2004 Scott James Remnant . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi + +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# +# Similar to PKG_CHECK_MODULES, make sure that the first instance of +# this or PKG_CHECK_MODULES is called, or make sure to call +# PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_ifval([$2], [$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + ifelse([$4], , [AC_MSG_ERROR(dnl +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT +])], + [AC_MSG_RESULT([no]) + $4]) +elif test $pkg_failed = untried; then + ifelse([$4], , [AC_MSG_FAILURE(dnl +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])], + [$4]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + ifelse([$3], , :, [$3]) +fi[]dnl +])# PKG_CHECK_MODULES + # po.m4 serial 15 (gettext-0.17) dnl Copyright (C) 1995-2007 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation diff -up system-config-printer-1.1.10/configure.in.525e996 system-config-printer-1.1.10/configure.in --- system-config-printer-1.1.10/configure.in.525e996 2009-07-22 13:53:25.000000000 +0100 +++ system-config-printer-1.1.10/configure.in 2009-07-26 19:01:45.685355364 +0100 @@ -40,6 +40,9 @@ AC_ARG_WITH(udev-rules, AM_CONDITIONAL([UDEV_RULES], [test x$with_udev_rules != xno]) if test x$with_udev_rules != xno; then + PKG_CHECK_MODULES(DBUS_GLIB, [dbus-glib-1 >= 0.76]) + AC_SUBST(DBUS_GLIB_CFLAGS) + AC_SUBST(DBUS_GLIB_LIBS) AM_PROG_CC_C_O fi diff -up system-config-printer-1.1.10/cupshelpers/ppds.py.525e996 system-config-printer-1.1.10/cupshelpers/ppds.py --- system-config-printer-1.1.10/cupshelpers/ppds.py.525e996 2009-07-22 13:22:49.000000000 +0100 +++ system-config-printer-1.1.10/cupshelpers/ppds.py 2009-07-26 19:01:45.686355890 +0100 @@ -159,29 +159,32 @@ def ppdMakeModelSplit (ppd_make_and_mode # HP PPDs give NickNames like: # *NickName: "HP LaserJet 4 Plus v2013.111 Postscript (recommended)" # Find the version number. - v = model.find (" v") + modell = model.lower () + v = modell.find (" v") if v != -1 and (model[v + 2].isdigit () or (model[v + 2] == '.' and model[v + 3].isdigit ())): # Truncate at that point. model = model[:v] + modell = modell[:v] for suffix in [" hpijs", - " Foomatic/", + " foomatic/", " - ", " w/", " (", - " PostScript", - " PS", - " PS1", - " PS2", - " PS3", - " PXL", - " series" + " postscript", + " ps", + " ps1", + " ps2", + " ps3", + " pxl", + " series", ","]: - s = model.find (suffix) + s = modell.find (suffix) if s != -1: model = model[:s] + modell = modell[:s] if makel == "hp": modelnames = {"dj": "DeskJet", @@ -190,10 +193,10 @@ def ppdMakeModelSplit (ppd_make_and_mode "color lj": "Color LaserJet", "ps ": "PhotoSmart", "hp ": ""} - modell = model.lower () for (name, fullname) in modelnames.iteritems (): if modell.startswith (name): model = fullname + model[len (name):] + modell = model.lower () for mfr in [ "Apple", "Canon", "Epson", "Lexmark", "Oki" ]: if makel == mfr.lower (): diff -up system-config-printer-1.1.10/Makefile.am.525e996 system-config-printer-1.1.10/Makefile.am --- system-config-printer-1.1.10/Makefile.am.525e996 2009-07-22 15:11:41.000000000 +0100 +++ system-config-printer-1.1.10/Makefile.am 2009-07-26 19:01:45.680355779 +0100 @@ -152,14 +152,65 @@ bin_SCRIPTS=\ if UDEV_RULES udevrulesdir=$(sysconfdir)/udev/rules.d udevrules_DATA=udev/70-printers.rules -udev_udev_configure_printer_SOURCES=\ - udev/udev-configure-printer.c -udev_udev_configure_printer_LDADD=-lcups -ludev -lusb + +udev_printer_config_daemon_SOURCES= \ + udev/printer-config-daemon.c \ + udev/printer-config-main.c \ + udev/printer-config.h +udev_printer_config_daemon_DEPENDENCIES= \ + udev/printer-config-server-bindings.h +udev_printer_config_daemon_CPPFLAGS= \ + $(DBUS_GLIB_CFLAGS) +udev_printer_config_daemon_LDADD= \ + $(DBUS_GLIB_LIBS) \ + -lcups -ludev -lusb + +udev_udev_usb_printer_SOURCES= \ + udev/udev-usb-printer.c +udev_udev_usb_printer_DEPENDENCIES= \ + udev/printer-config-client-bindings.h +udev_udev_usb_printer_CPPFLAGS= \ + $(DBUS_GLIB_CFLAGS) +udev_udev_usb_printer_LDADD= \ + $(DBUS_GLIB_LIBS) \ + -ludev -lusb udevhelperdir=/lib/udev udevhelper_PROGRAMS=\ - udev/udev-configure-printer -udevhelper_SCRIPTS=\ + udev/udev-usb-printer + +udev/printer-config-server-bindings.h: udev/com.redhat.PrinterConfig.xml \ + Makefile + dbus-binding-tool --prefix=printer_config_daemon \ + --mode=glib-server \ + --output=$@ $< + +udev/printer-config-client-bindings.h: udev/com.redhat.PrinterConfig.xml \ + Makefile + dbus-binding-tool --prefix=printer_config_daemon \ + --mode=glib-client \ + --output=$@ $< + +BUILT_SOURCES= \ + udev/printer-config-server-bindings.h \ + udev/printer-config-client-bindings.h + +libexec_PROGRAMS=\ + udev/printer-config-daemon +libexec_SCRIPTS=\ udev/udev-add-printer + +dbusifdir = $(datadir)/dbus-1/interfaces +dbusif_DATA = udev/com.redhat.PrinterConfig.xml + +servicedir = $(datadir)/dbus-1/system-services +service_in_files = udev/com.redhat.PrinterConfig.service.in +service_DATA = $(service_in_files:.service.in=.service) + +$(service_DATA): $(service_in_files) Makefile + @sed -e "s,\@libexecdir\@,$(libexecdir)," $< > $@ + +dbusconfdir = $(sysconfdir)/dbus-1/system.d +dbusconf_DATA = udev/com.redhat.PrinterConfig.conf endif man_MANS= \ @@ -238,7 +289,10 @@ EXTRA_DIST=\ intltool-merge.in \ intltool-update.in \ config.py.in \ - udev/70-printers.rules + udev/70-printers.rules \ + udev/com.redhat.PrinterConfig.xml \ + udev/com.redhat.PrinterConfig.service.in \ + udev/com.redhat.PrinterConfig.conf desktop_in_files = $(desktop_DATA:.desktop=.desktop.in) @@ -293,12 +347,17 @@ test-ppd-module.sh: TESTS = test-ppd-module.sh +CLEANFILES = \ + $(BUILT_SOURCES) \ + udev/*.o + DISTCLEANFILES=*.pyc *.pyo *~ *.bak \ troubleshoot/*.pyc troubleshoot/*.pyo troubleshoot/*~ \ intltool-extract intltool-merge intltool-update \ *.desktop man/*.1 \ test-ppd-module.sh pickled-ppds \ - config.py + config.py \ + $(service_DATA) distclean-local: rm -rf html diff -up system-config-printer-1.1.10/system-config-printer.py.525e996 system-config-printer-1.1.10/system-config-printer.py --- system-config-printer-1.1.10/system-config-printer.py.525e996 2009-07-21 15:16:35.000000000 +0100 +++ system-config-printer-1.1.10/system-config-printer.py 2009-07-26 19:01:45.690356122 +0100 @@ -3377,7 +3377,7 @@ class GUI(GtkGUI, monitor.Watcher): name = name.replace ("#", "-") if not self.checkNPName (name): suffix=2 - while not self.checkNPName (name + str (suffix)): + while not self.checkNPName (name + "-" + str (suffix)): suffix += 1 if suffix == 100: break diff -up system-config-printer-1.1.10/udev/70-printers.rules.525e996 system-config-printer-1.1.10/udev/70-printers.rules --- system-config-printer-1.1.10/udev/70-printers.rules.525e996 2009-07-22 13:22:49.000000000 +0100 +++ system-config-printer-1.1.10/udev/70-printers.rules 2009-07-26 19:01:45.698481110 +0100 @@ -1,7 +1,7 @@ # Low-level USB device add trigger -ACTION=="add", SUBSYSTEM=="usb", ATTR{bInterfaceClass}=="07", ATTR{bInterfaceSubClass}=="01", RUN+="udev-configure-printer add %p" +ACTION=="add", SUBSYSTEM=="usb", ATTR{bInterfaceClass}=="07", ATTR{bInterfaceSubClass}=="01", RUN+="udev-usb-printer add %p" # usblp device add trigger (needed when usblp is already loaded) -ACTION=="add", KERNEL=="lp*", RUN+="udev-configure-printer add %p" +ACTION=="add", KERNEL=="lp*", RUN+="udev-usb-printer add %p" # Low-level USB device remove trigger -ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="*:0701*:*", RUN+="udev-configure-printer remove %p" +ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_USB_INTERFACES}=="*:0701*:*", RUN+="udev-usb-printer remove %p" diff -up /dev/null system-config-printer-1.1.10/udev/com.redhat.PrinterConfig.conf --- /dev/null 2009-07-26 12:31:15.305032486 +0100 +++ system-config-printer-1.1.10/udev/com.redhat.PrinterConfig.conf 2009-07-26 19:01:45.698481110 +0100 @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff -up /dev/null system-config-printer-1.1.10/udev/com.redhat.PrinterConfig.service.in --- /dev/null 2009-07-26 12:31:15.305032486 +0100 +++ system-config-printer-1.1.10/udev/com.redhat.PrinterConfig.service.in 2009-07-26 19:01:45.699481020 +0100 @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=com.redhat.PrinterConfig +Exec=@libexecdir@/printer-config-daemon +User=root diff -up /dev/null system-config-printer-1.1.10/udev/com.redhat.PrinterConfig.xml --- /dev/null 2009-07-26 12:31:15.305032486 +0100 +++ system-config-printer-1.1.10/udev/com.redhat.PrinterConfig.xml 2009-07-26 19:01:45.699481020 +0100 @@ -0,0 +1,71 @@ + + + + + + + The PrinterConfig service is available via the + system message bus. To access the service, use the + com.redhat.PrinterConfig interface + on the /com/redhat/PrinterConfig + object on the D-Bus system bus service with the well-known + name + com.redhat.PrinterConfig. + + It is intended to be used by udev when USB printers + are connected and disconnected. + + + + + + + + + The udev devpath of the device with subsystem + usb and devtype usb_device referring to the USB printer + device. + + + + + + + + The IEEE 1284 Device ID (not including length + field) of the device. + + + + + + + This method informs the PrinterConfig service that + a USB printer is now connected. + + + + + + + + + + The udev devpath of the device with subsystem + usb and devtype usb_device referring to the USB printer + device. + + + + + + + This method informs the PrinterConfig service that + a USB printer has been disconnected. + + + + + diff -up /dev/null system-config-printer-1.1.10/udev/printer-config-daemon.c --- /dev/null 2009-07-26 12:31:15.305032486 +0100 +++ system-config-printer-1.1.10/udev/printer-config-daemon.c 2009-07-26 19:01:45.701480595 +0100 @@ -0,0 +1,1248 @@ +/* -*- Mode: C; c-file-style: "gnu" -*- + * printer-config-daemon - a D-Bus service for configuring printers + * Copyright (C) 2009 Red Hat, Inc. + * Author: Tim Waugh + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE 1 +#include "printer-config.h" +#include "printer-config-server-bindings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DISABLED_REASON "Unplugged or turned off" +#define MATCH_ONLY_DISABLED 1 +#define USB_URI_MAP "/var/run/udev-configure-printer/usb-uris" + +struct children +{ + struct children *next; + GPid pid; +}; + +struct device_uris +{ + size_t n_uris; + char **uri; +}; + +struct usb_uri_map_entry +{ + struct usb_uri_map_entry *next; + + /* The devpath of the ("usb","usb_device") device. */ + char *devpath; + + /* List of matching device URIs. */ + struct device_uris uris; +}; + +struct usb_uri_map +{ + struct usb_uri_map_entry *entries; + + /* Open file descriptor for the map, or -1 if it has already been + * written. */ + int fd; +}; + +struct device_id +{ + char *full_device_id; + char *mfg; + char *mdl; + char *sern; +}; + +/* Device URI schemes in decreasing order of preference. */ +static const char *device_uri_types[] = + { + "hp", + "usb", + }; + +static int +device_uri_type (const char *uri) +{ + int slen = strcspn (uri, ":"); + int i; + int n = sizeof (device_uri_types) / sizeof (device_uri_types[0]); + for (i = 0; i < n; i++) + if (!strncmp (uri, device_uri_types[i], slen) && + device_uri_types[i][slen] == '\0') + break; + + return i; +} + +static void +add_device_uri (struct device_uris *uris, + const char *uri) +{ + char *uri_copy = strdup (uri); + if (!uri_copy) + { + syslog (LOG_ERR, "out of memory"); + return; + } + + if (uris->n_uris == 0) + { + uris->uri = malloc (sizeof (char *)); + if (uris->uri) + { + uris->n_uris = 1; + uris->uri[0] = uri_copy; + } + } + else + { + char **old = uris->uri; + if (++uris->n_uris < UINT_MAX / sizeof (char *)) + { + uris->uri = realloc (uris->uri, + sizeof (char *) * uris->n_uris); + if (uris->uri) + uris->uri[uris->n_uris - 1] = uri_copy; + else + { + uris->uri = old; + uris->n_uris--; + free (uri_copy); + } + } + else + { + uris->n_uris--; + free (uri_copy); + } + } +} + +static void +free_device_uris (struct device_uris *uris) +{ + size_t i; + for (i = 0; i < uris->n_uris; i++) + free (uris->uri[i]); + free (uris->uri); +} + +static void +add_usb_uri_mapping (struct usb_uri_map **map, + const char *devpath, + const struct device_uris *uris) +{ + struct usb_uri_map_entry *entry, **prev; + size_t i; + prev = &(*map)->entries; + while (*prev) + prev = &((*prev)->next); + + entry = malloc (sizeof (struct usb_uri_map_entry)); + if (!entry) + { + syslog (LOG_ERR, "out of memory"); + return; + } + + entry->devpath = strdup (devpath); + entry->uris.n_uris = uris->n_uris; + entry->uris.uri = malloc (sizeof (char *) * uris->n_uris); + for (i = 0; i < uris->n_uris; i++) + entry->uris.uri[i] = strdup (uris->uri[i]); + entry->next = NULL; + *prev = entry; +} + +static struct usb_uri_map * +read_usb_uri_map (void) +{ + int fd = open (USB_URI_MAP, O_RDWR); + struct usb_uri_map *map = NULL; + struct flock lock; + struct stat st; + char *buf, *line; + + if (fd == -1) + { + char dir[] = USB_URI_MAP; + char *p = strrchr (dir, '/'); + if (p) + { + *p = '\0'; + mkdir (dir, 0755); + fd = open (USB_URI_MAP, O_RDWR | O_TRUNC | O_CREAT, 0644); + if (fd == -1) + { + syslog (LOG_ERR, "failed to create " USB_URI_MAP); + return NULL; + } + } + } + + map = malloc (sizeof (struct usb_uri_map)); + if (!map) + { + close (fd); + syslog (LOG_ERR, "out of memory"); + return NULL; + } + + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl (fd, F_SETLKW, &lock) == -1) + { + close (fd); + free (map); + syslog (LOG_ERR, "failed to lock " USB_URI_MAP); + return NULL; + } + + map->entries = NULL; + map->fd = fd; + if (fstat (fd, &st) == -1) + { + close (fd); + free (map); + syslog (LOG_ERR, "failed to fstat " USB_URI_MAP " (fd %d)", fd); + return NULL; + } + + /* Read the entire file into memory. */ + buf = malloc (1 + (sizeof (char) * st.st_size)); + if (!buf) + { + close (fd); + free (map); + syslog (LOG_ERR, "out of memory"); + return NULL; + } + + if (read (fd, buf, st.st_size) < 0) + { + close (fd); + free (map); + free (buf); + syslog (LOG_ERR, "failed to read " USB_URI_MAP); + return NULL; + } + + buf[st.st_size] = '\0'; + line = buf; + while (line) + { + char *saveptr = NULL; + const char *devpath, *uri; + struct device_uris uris; + char *nextline = strchr (line, '\n'); + if (!nextline) + break; + + *nextline++ = '\0'; + if (nextline >= buf + st.st_size) + nextline = NULL; + + devpath = strtok_r (line, "\t", &saveptr); + uri = strtok_r (NULL, "\t", &saveptr); + if (!devpath || !uri) + { + syslog (LOG_DEBUG, "Incorrect line in " USB_URI_MAP ": %s", + line); + continue; + } + + uris.n_uris = 1; + uris.uri = malloc (sizeof (char *)); + if (uris.uri == NULL) + break; + + uris.uri[0] = strdup (uri); + while ((uri = strtok_r (NULL, "\t", &saveptr)) != NULL) + add_device_uri (&uris, uri); + + add_usb_uri_mapping (&map, devpath, &uris); + + line = nextline; + } + + free (buf); + return map; +} + +static void +write_usb_uri_map (struct usb_uri_map *map) +{ + struct usb_uri_map_entry *entry; + int fd = map->fd; + FILE *f; + + lseek (fd, SEEK_SET, 0); + ftruncate (fd, 0); + f = fdopen (fd, "w"); + if (!f) + { + syslog (LOG_ERR, "failed to fdopen " USB_URI_MAP " (fd %d)", fd); + close (fd); + map->fd = -1; + return; + } + + for (entry = map->entries; entry; entry = entry->next) + { + size_t i; + fprintf (f, "%s\t%s", entry->devpath, entry->uris.uri[0]); + for (i = 1; i < entry->uris.n_uris; i++) + fprintf (f, "\t%s", entry->uris.uri[i]); + fwrite ("\n", 1, 1, f); + } + + fclose (f); + map->fd = -1; +} + +static void +free_usb_uri_map (struct usb_uri_map *map) +{ + struct usb_uri_map_entry *entry, *next; + for (entry = map->entries; entry; entry = next) + { + next = entry->next; + free (entry->devpath); + free_device_uris (&entry->uris); + free (entry); + } + + if (map->fd != -1) + close (map->fd); + + free (map); +} + +static void +free_device_id (struct device_id *id) +{ + free (id->full_device_id); + free (id->mfg); + free (id->mdl); + free (id->sern); +} + +static void +parse_device_id (const char *device_id, + struct device_id *id) +{ + char *fieldname; + char *start, *end; + size_t len; + + len = strlen (device_id); + if (len == 0) + return; + + if (device_id[len - 1] == '\n') + len--; + + id->full_device_id = malloc (len + 1); + fieldname = malloc (len + 1); + if (!id->full_device_id || !fieldname) + { + syslog (LOG_ERR, "out of memory"); + return; + } + + memcpy (id->full_device_id, device_id, len); + id->full_device_id[len] = '\0'; + fieldname[0] = '\0'; + start = id->full_device_id; + while (*start != '\0') + { + /* New field. */ + + end = start; + while (*end != '\0' && *end != ':') + end++; + + if (*end == '\0') + break; + + len = end - start; + memcpy (fieldname, start, len); + fieldname[len] = '\0'; + + start = end + 1; + while (*end != '\0' && *end != ';') + end++; + + len = end - start; + + if (!id->mfg && + (!strncasecmp (fieldname, "MANUFACTURER", 12) || + !strncasecmp (fieldname, "MFG", 3))) + id->mfg = strndup (start, len); + else if (!id->mdl && + (!strncasecmp (fieldname, "MODEL", 5) || + !strncasecmp (fieldname, "MDL", 3))) + id->mdl = strndup (start, len); + else if (!id->sern && + (!strncasecmp (fieldname, "SERIALNUMBER", 12) || + !strncasecmp (fieldname, "SERN", 4) || + !strncasecmp (fieldname, "SN", 2))) + id->sern = strndup (start, len); + + if (*end != '\0') + start = end + 1; + } + + free (fieldname); +} + +static const char * +no_password (const char *prompt) +{ + return ""; +} + +static http_t * +cups_connection (void) +{ + http_t *cups = NULL; + static int first_time = 1; + + if (first_time) + { + cupsSetPasswordCB (no_password); + first_time = 0; + } + + cups = httpConnectEncrypt ("localhost", 631, + HTTP_ENCRYPT_IF_REQUESTED); + if (cups == NULL) + { + /* Don't bother retrying here. Instead, the CUPS initscript + should run these commands after cupsd is started: + + rmmod usblp + udevadm trigger --subsystem-match=usb \ + --attr-match=bInterfaceClass=07 \ + --attr-match=bInterfaceSubClass=01 + */ + + syslog (LOG_DEBUG, "failed to connect to CUPS server; giving up"); + return NULL; + } + + return cups; +} + +static int +find_matching_device_uris (struct device_id *id, + const char *usbserial, + struct device_uris *uris, + const char *devpath, + struct usb_uri_map *map) +{ + http_t *cups; + ipp_t *request, *answer; + ipp_attribute_t *attr; + struct device_uris uris_noserial; + struct device_uris all_uris; + size_t i, n; + const char *exclude_schemes[] = { + "beh", + "bluetooth", + "http", + "https", + "ipp", + "lpd", + "ncp", + "parallel", + "scsi", + "smb", + "snmp", + "socket", + }; + + uris->n_uris = uris_noserial.n_uris = all_uris.n_uris = 0; + uris->uri = uris_noserial.uri = all_uris.uri = NULL; + + /* Leave the bus to settle. */ + sleep (1); + + cups = cups_connection (); + request = ippNewRequest (CUPS_GET_DEVICES); + ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_NAME, "exclude-schemes", + sizeof (exclude_schemes) / sizeof(exclude_schemes[0]), + NULL, exclude_schemes); + + answer = cupsDoRequest (cups, request, "/"); + httpClose (cups); + + if (answer == NULL) + { + syslog (LOG_ERR, "failed to send IPP request %d", + request->request.op.operation_id); + return 0; + } + + if (answer->request.status.status_code > IPP_OK_CONFLICT) + { + syslog (LOG_ERR, "IPP request %d failed (%d)", + request->request.op.operation_id, + answer->request.status.status_code); + return 0; + } + + for (attr = answer->attrs; attr; attr = attr->next) + { + const char *device_uri = NULL; + struct device_id this_id; + this_id.full_device_id = this_id.mfg = this_id.mdl = this_id.sern = NULL; + + while (attr && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (!attr) + break; + + for (; attr && attr->group_tag == IPP_TAG_PRINTER; attr = attr->next) + { + if (attr->value_tag == IPP_TAG_URI && + !strcmp (attr->name, "device-uri")) + device_uri = attr->values[0].string.text; + else if (attr->value_tag == IPP_TAG_TEXT && + !strcmp (attr->name, "device-id")) + parse_device_id (attr->values[0].string.text, &this_id); + } + + /* Only use device schemes in our preference order for matching + * against the IEEE 1284 Device ID. */ + + for (i = 0; + device_uri && + i < sizeof (device_uri_types) / sizeof (device_uri_types[0]); + i++) + { + size_t len = strlen (device_uri_types[i]); + if (!strncmp (device_uri_types[i], device_uri, len) && + device_uri[len] == ':') + break; + } + + if (device_uri) + add_device_uri (&all_uris, device_uri); + + if (i == sizeof (device_uri_types) / sizeof (device_uri_types[0])) + /* Not what we want to match against. Ignore this one. */ + device_uri = NULL; + + /* Now check the manufacturer and model names. */ + if (device_uri && this_id.mfg && this_id.mdl && + !strcmp (this_id.mfg, id->mfg) && + !strcmp (this_id.mdl, id->mdl)) + { + /* We've checked everything except the serial numbers. This + * is more complicated. Some devices include a serial + * number (SERN) field in their IEEE 1284 Device ID. Others + * don't -- this was not a mandatory field in the + * specification. + * + * If the device includes SERN field in its, it must match + * what the device-id attribute has. + * + * Otherwise, the only means we have of knowing which device + * is meant is the USB serial number. + * + * CUPS backends may choose to insert the USB serial number + * into the SERN field when reporting a device-id attribute. + * HPLIP does this, and it seems not to stray too far from + * the intent of that field. We accommodate this. + * + * Alternatively, CUPS backends may include the USB serial + * number somewhere in their reported device-uri attributes. + * For instance, the CUPS 1.4 usb backend, when compiled + * with libusb support, gives device URIs containing the USB + * serial number for devices without a SERN field, like + * this: usb://HP/DESKJET%20990C?serial=US05M1D20CIJ + * + * To accommodate this we examine tokens between '?', '=' + * and '&' delimiters to check for USB serial number + * matches. + * + * CUPS 1.3, and CUPS 1.4 without libusb support, doesn't do this. + * As a result we also need to deal with devices that don't report a + * SERN field where the backends that don't add a SERN field from + * the USB serial number and also don't include the USB serial + * number in the URI. + */ + + int match = 0; + if ((id->sern && this_id.sern && !strcmp (id->sern, this_id.sern))) + { + syslog (LOG_DEBUG, "SERN fields match"); + match = 1; + } + + if (!match && usbserial[0] != '\0') + { + if (!id->sern) + { + if (this_id.sern && !strcmp (usbserial, this_id.sern)) + { + syslog (LOG_DEBUG, + "SERN field matches USB serial number"); + match = 1; + } + } + + if (!match) + { + char *saveptr, *uri = strdup (device_uri); + const char *token; + const char *sep = "?=&/"; + for (token = strtok_r (uri, sep, &saveptr); + token; + token = strtok_r (NULL, sep, &saveptr)) + if (!strcmp (token, usbserial)) + { + syslog (LOG_DEBUG, "URI contains USB serial number"); + match = 1; + break; + } + + free (uri); + } + } + + if (match) + { + syslog (LOG_DEBUG, "URI match: %s", device_uri); + add_device_uri (uris, device_uri); + } + else if (!id->sern) + { + syslog (LOG_DEBUG, "URI matches without serial number: %s", + device_uri); + add_device_uri (&uris_noserial, device_uri); + } + else + syslog (LOG_DEBUG, "No match: %s", device_uri); + } + + if (!attr) + break; + } + + ippDelete (answer); + + /* Decide what to do about device URIs that did not match a serial + * number. The device had no SERN field, and the USB serial number + * was nowhere to be found from the device URI or device-id field. + * + * Device URIs with no reference to serial number can only each ever + * work when only one printer of that model is connected. + * Accordingly, it is safe to disable queues using such URIs, as we + * know the removed/added device is that lone printer. + * + * When adding queues it is best to avoid URIs that don't + * distinguish serial numbers. + * + * What we'll do, then, is concatenate the list of "non-serial" URIs + * onto the end of the list of "serial" URIs. + */ + + if (uris->n_uris == 0 && uris_noserial.n_uris > 0) + { + syslog (LOG_DEBUG, "No serial number URI matches so using those without"); + uris->n_uris = uris_noserial.n_uris; + uris->uri = uris_noserial.uri; + uris_noserial.n_uris = 0; + uris_noserial.uri = NULL; + } + else if (uris_noserial.n_uris > 0) + { + char **old = uris->uri; + uris->uri = realloc (uris->uri, + sizeof (char *) * (uris->n_uris + + uris_noserial.n_uris)); + if (!uris->uri) + uris->uri = old; + else + { + for (i = 0; i < uris_noserial.n_uris; i++) + uris->uri[uris->n_uris + i] = uris_noserial.uri[i]; + uris->n_uris += uris_noserial.n_uris; + } + + uris_noserial.n_uris = 0; + uris_noserial.uri = NULL; + } + + free_device_uris (&uris_noserial); + + /* Having decided which device URIs match based on IEEE 1284 Device + * ID, we now need to look for "paired" URIs for other functions of + * a multi-function device. This are the same except for the + * scheme. */ + + n = uris->n_uris; + for (i = 0; i < n; i++) + { + size_t j; + char *me = uris->uri[i]; + char *my_rest = strchr (me, ':'); + size_t my_schemelen; + if (!my_rest) + continue; + + my_schemelen = my_rest - me; + for (j = 0; j < all_uris.n_uris; j++) + { + char *twin = all_uris.uri[j]; + char *twin_rest = strchr (twin, ':'); + size_t twin_schemelen; + if (!twin_rest) + continue; + + twin_schemelen = twin_rest - twin; + if (my_schemelen == twin_schemelen && + !strncmp (me, twin, my_schemelen)) + /* This is the one we are looking for the twin of. */ + continue; + + if (!strcmp (my_rest, twin_rest)) + { + syslog (LOG_DEBUG, "%s twinned with %s", me, twin); + add_device_uri (uris, twin); + } + } + } + + free_device_uris (&all_uris); + if (uris->n_uris > 0) + { + add_usb_uri_mapping (&map, devpath, uris); + write_usb_uri_map (map); + free_usb_uri_map (map); + } + + return uris->n_uris; +} + +/* Call a function for each queue with the given device-uri and printer-state. + * Returns the number of queues with a matching device-uri. */ +static size_t +for_each_matching_queue (struct device_uris *device_uris, + int flags, + void (*fn) (const char *, void *), + void *context) +{ + size_t matched = 0; + http_t *cups = cups_connection (); + ipp_t *request, *answer; + ipp_attribute_t *attr; + const char *attributes[] = { + "printer-uri-supported", + "device-uri", + "printer-state", + "printer-state-message", + }; + + request = ippNewRequest (CUPS_GET_PRINTERS); + ippAddStrings (request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, + "requested-attributes", + sizeof (attributes) / sizeof (attributes[0]), + NULL, attributes); + answer = cupsDoRequest (cups, request, "/"); + httpClose (cups); + if (answer == NULL) + { + syslog (LOG_ERR, "failed to send CUPS-Get-Printers request"); + return 0; + } + + if (answer->request.status.status_code > IPP_OK_CONFLICT) + { + if (answer->request.status.status_code == IPP_NOT_FOUND) + { + /* No printer queues configured. */ + ippDelete (answer); + return 0; + } + + syslog (LOG_ERR, "CUPS-Get-Printers request failed (%d)", + answer->request.status.status_code); + return 0; + } + + for (attr = answer->attrs; attr; attr = attr->next) + { + const char *this_printer_uri = NULL; + const char *this_device_uri = NULL; + const char *printer_state_message = NULL; + int state = 0; + size_t i; + + while (attr && attr->group_tag != IPP_TAG_PRINTER) + attr = attr->next; + + if (!attr) + break; + + for (; attr && attr->group_tag == IPP_TAG_PRINTER; attr = attr->next) + { + if (attr->value_tag == IPP_TAG_URI) + { + if (!strcmp (attr->name, "device-uri")) + this_device_uri = attr->values[0].string.text; + else if (!strcmp (attr->name, "printer-uri-supported")) + this_printer_uri = attr->values[0].string.text; + } + else if (attr->value_tag == IPP_TAG_TEXT && + !strcmp (attr->name, "printer-state-message")) + printer_state_message = attr->values[0].string.text; + else if (attr->value_tag == IPP_TAG_ENUM && + !strcmp (attr->name, "printer-state")) + state = attr->values[0].integer; + } + + for (i = 0; i < device_uris->n_uris; i++) + if (!strcmp (device_uris->uri[i], this_device_uri)) + { + matched++; + if (((flags & MATCH_ONLY_DISABLED) && + state == IPP_PRINTER_STOPPED && + !strcmp (printer_state_message, DISABLED_REASON)) || + (flags & MATCH_ONLY_DISABLED) == 0) + { + syslog (LOG_DEBUG ,"Queue %s has matching device URI", + this_printer_uri); + (*fn) (this_printer_uri, context); + } + } + + if (!attr) + break; + } + + ippDelete (answer); + return matched; +} + +static void +enable_queue (const char *printer_uri, void *context) +{ + /* Disable it. */ + http_t *cups = cups_connection (); + ipp_t *request, *answer; + request = ippNewRequest (IPP_RESUME_PRINTER); + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, printer_uri); + answer = cupsDoRequest (cups, request, "/admin/"); + if (!answer) + { + syslog (LOG_ERR, "Failed to send IPP-Resume-Printer request"); + httpClose (cups); + return; + } + + if (answer->request.status.status_code > IPP_OK_CONFLICT) + syslog (LOG_ERR, "IPP-Resume-Printer request failed"); + else + syslog (LOG_INFO, "Re-enabled printer %s", printer_uri); + + ippDelete (answer); + httpClose (cups); +} + +static void +disable_queue (const char *printer_uri, void *context) +{ + /* Disable it. */ + http_t *cups = cups_connection (); + ipp_t *request, *answer; + request = ippNewRequest (IPP_PAUSE_PRINTER); + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, printer_uri); + ippAddString (request, IPP_TAG_OPERATION, IPP_TAG_TEXT, + "printer-state-message", NULL, DISABLED_REASON); + answer = cupsDoRequest (cups, request, "/admin/"); + if (!answer) + { + syslog (LOG_ERR, "Failed to send IPP-Pause-Printer request"); + httpClose (cups); + return; + } + + if (answer->request.status.status_code > IPP_OK_CONFLICT) + syslog (LOG_ERR, "IPP-Pause-Printer request failed"); + else + syslog (LOG_INFO, "Disabled printer %s as the corresponding device " + "was unplugged or turned off", printer_uri); + + ippDelete (answer); + httpClose (cups); +} + +static char * +syspath_from_devpath (struct udev *udev, const char *devpath) +{ + const char *sys; + char *syspath; + size_t syslen, devpathlen = strlen (devpath); + sys = udev_get_sys_path (udev); + syslen = strlen (sys); + syspath = malloc (syslen + devpathlen + 1); + if (syspath == NULL) + return NULL; + + memcpy (syspath, sys, syslen); + memcpy (syspath + syslen, devpath, devpathlen); + syspath[syslen + devpathlen] = '\0'; + return syspath; +} + +static void +reap_child (GPid pid, gint status, gpointer context) +{ + PrinterConfigDaemon *self = context; + struct children *child, **prev = &self->children; + g_debug ("PID %d has exited", pid); + for (child = self->children; child; prev = &child->next, child = child->next) + if (child->pid == pid) + { + *prev = child->next; + free (child); + g_debug ("self->children is now %p", self->children); + break; + } +} + +G_DEFINE_TYPE (PrinterConfigDaemon, printer_config_daemon, G_TYPE_OBJECT) + +static gboolean +kill_timeout (gpointer context) +{ + PrinterConfigDaemon *self = context; + if (self->children == NULL) + { + g_debug ("Time to go"); + main_quit (); + return FALSE; + } + + g_debug ("children is %p", self->children); + return TRUE; +} + +static gboolean +reset_killtimer (PrinterConfigDaemon *self) +{ + if (self->killtimer != 0) + { + g_debug ("Remove killtimer %d", self->killtimer); + g_source_remove (self->killtimer); + } + + self->killtimer = g_timeout_add (1000, kill_timeout, self); + g_debug ("Set killtimer %d", self->killtimer); + return TRUE; +} + +static void +printer_config_daemon_dispose (GObject *gobject) +{ + PrinterConfigDaemon *self = PRINTER_CONFIG_DAEMON (gobject); + g_debug ("dispose %p", self); + G_OBJECT_CLASS (printer_config_daemon_parent_class)->dispose (gobject); +} + +static void +printer_config_daemon_finalize (GObject *gobject) +{ + PrinterConfigDaemon *self = PRINTER_CONFIG_DAEMON (gobject); + g_debug ("finalize %p", self); + if (self->killtimer != 0) + { + g_debug ("Remove killtimer %d", self->killtimer); + g_source_remove (self->killtimer); + } + + G_OBJECT_CLASS (printer_config_daemon_parent_class)->finalize (gobject); +} + +static void +printer_config_daemon_class_init (PrinterConfigDaemonClass *klass) +{ + GError *error = NULL; + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = printer_config_daemon_dispose; + gobject_class->finalize = printer_config_daemon_finalize; + g_debug ("class init"); + klass->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (klass->connection == NULL) + { + g_warning ("Unable to connect to D-Bus: %s", error->message); + g_error_free (error); + return; + } + + dbus_g_object_type_install_info(PRINTER_CONFIG_TYPE_DAEMON, + &dbus_glib_printer_config_daemon_object_info); +} + +static void +printer_config_daemon_init (PrinterConfigDaemon *self) +{ + DBusGProxy *driver_proxy; + GError *error = NULL; + PrinterConfigDaemonClass *klass = PRINTER_CONFIG_DAEMON_GET_CLASS (self); + guint request_ret; + + dbus_g_connection_register_g_object (klass->connection, + "/com/redhat/PrinterConfig", + G_OBJECT (self)); + driver_proxy = dbus_g_proxy_new_for_name (klass->connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + + g_debug ("daemon init %p", self); + if (!org_freedesktop_DBus_request_name (driver_proxy, + "com.redhat.PrinterConfig", + 0, &request_ret, + &error)) + { + g_warning ("Unable to register service: %s", error->message); + g_error_free (error); + } + + self->children = NULL; + g_object_unref (driver_proxy); +} + +gboolean +printer_config_daemon_usb_printer_add (PrinterConfigDaemon *self, + const char *usb_device_devpath, + const char *deviceid, + DBusGMethodInvocation *context) +{ + struct device_id id; + struct device_uris device_uris; + struct usb_uri_map *map; + struct usb_uri_map_entry *entry; + struct udev *udev; + struct udev_device *dev; + const char *usbserial; + char *syspath; + + syslog (LOG_DEBUG, "add %s", usb_device_devpath); + dbus_g_method_return (context); + + reset_killtimer (self); + udev = udev_new (); + if (!udev) + { + syslog (LOG_ERR, "failed to init libudev"); + return FALSE; + } + + map = read_usb_uri_map (); + if (!map) + return TRUE; + + for (entry = map->entries; entry; entry = entry->next) + if (!strcmp (entry->devpath, usb_device_devpath)) + break; + + if (entry != NULL) + /* The map already had an entry so has already been dealt + * with. This can happen because there are two "add" + * triggers: one for the usb_device device and the other for + * the usblp device. We have most likely been triggered by + * the usblp device, so the usb_device rule got there before + * us and succeeded. + * + * Pretend we didn't find any device URIs that matched, and + * exit. + */ + return TRUE; + + id.full_device_id = id.mfg = id.mdl = id.sern = NULL; + parse_device_id (deviceid, &id); + if (!id.mfg || !id.mdl) + { + syslog (LOG_ERR, "invalid IEEE 1284 Device ID %s", + id.full_device_id); + return FALSE; + } + + syspath = syspath_from_devpath (udev, usb_device_devpath); + if (!syspath) + { + syslog (LOG_ERR, "unable to get syspath from devpath"); + return FALSE; + } + + dev = udev_device_new_from_syspath (udev, syspath); + if (!dev) + { + udev_device_unref (dev); + udev_unref (udev); + syslog (LOG_ERR, "unable to access %s", syspath); + free (syspath); + return TRUE; + } + + free (syspath); + usbserial = udev_device_get_sysattr_value (dev, "serial"); + syslog (LOG_DEBUG, "MFG:%s MDL:%s SERN:%s serial:%s", id.mfg, id.mdl, + id.sern ? id.sern : "-", usbserial ? usbserial : "-"); + + find_matching_device_uris (&id, usbserial, &device_uris, usb_device_devpath, + map); + udev_device_unref (dev); + udev_unref (udev); + if (device_uris.n_uris == 0) + { + free_device_id (&id); + return TRUE; + } + + /* Re-enable any queues we'd previously disabled. */ + if (for_each_matching_queue (&device_uris, MATCH_ONLY_DISABLED, + enable_queue, NULL) == 0) + { + size_t i; + int type; + GPid child_pid; + GError *error = NULL; + char **argv = malloc (sizeof (char *) * (3 + device_uris.n_uris)); + + /* No queue is configured for this device yet. + Decide on a URI to use. */ + type = device_uri_type (device_uris.uri[0]); + for (i = 1; i < device_uris.n_uris; i++) + { + int new_type = device_uri_type (device_uris.uri[i]); + if (new_type < type) + { + char *swap = device_uris.uri[0]; + device_uris.uri[0] = device_uris.uri[i]; + device_uris.uri[i] = swap; + type = new_type; + } + } + + argv[0] = "/usr/libexec/udev-add-printer"; + argv[1] = id.full_device_id; + for (i = 0; i < device_uris.n_uris; i++) + argv[i + 2] = device_uris.uri[i]; + argv[i + 2] = NULL; + + syslog (LOG_DEBUG, "About to add queue for %s", argv[2]); + if (g_spawn_async ("/", argv, NULL, + G_SPAWN_STDOUT_TO_DEV_NULL | + G_SPAWN_STDERR_TO_DEV_NULL | + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, + &child_pid, + &error) == FALSE) + syslog (LOG_ERR, "Failed to execute %s", argv[0]); + else + { + struct children *child = malloc (sizeof (struct children)); + child->next = self->children; + child->pid = child_pid; + self->children = child; + g_child_watch_add (child_pid, reap_child, self); + } + } + + free_device_id (&id); + free_device_uris (&device_uris); + return TRUE; +} + +gboolean +printer_config_daemon_usb_printer_remove (PrinterConfigDaemon *self, + const char *devpath, + DBusGMethodInvocation *context) +{ + struct usb_uri_map *map; + struct usb_uri_map_entry *entry, **prev; + struct device_uris *uris = NULL; + + syslog (LOG_DEBUG, "remove %s", devpath); + dbus_g_method_return (context); + + reset_killtimer (self); + map = read_usb_uri_map (); + if (!map) + return TRUE; + + prev = &map->entries; + for (entry = map->entries; entry; entry = entry->next) + { + if (!strcmp (entry->devpath, devpath)) + { + uris = &entry->uris; + break; + } + + prev = &(entry->next); + } + + if (uris) + { + /* Find the relevant queues and disable them if they are enabled. */ + for_each_matching_queue (uris, 0, disable_queue, NULL); + *prev = entry->next; + write_usb_uri_map (map); + } + + free_usb_uri_map (map); + return TRUE; +} + +PrinterConfigDaemon * +printer_config_daemon_new (void) +{ + PrinterConfigDaemon *self; + self = PRINTER_CONFIG_DAEMON (g_object_new (PRINTER_CONFIG_TYPE_DAEMON, + NULL)); + self->children = NULL; + self->killtimer = 0; + g_debug ("New daemon %p", self); + return self; +} diff -up /dev/null system-config-printer-1.1.10/udev/printer-config.h --- /dev/null 2009-07-26 12:31:15.305032486 +0100 +++ system-config-printer-1.1.10/udev/printer-config.h 2009-07-26 19:01:45.703480670 +0100 @@ -0,0 +1,76 @@ +/* -*- Mode: C; c-file-style: "gnu" -*- + * printer-config - a D-Bus service for configuring printers + * Copyright (C) 2009 Red Hat, Inc. + * Author: Tim Waugh + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PRINTER_CONFIG_H +#define PRINTER_CONFIG_H + +#include +#include + +#define PRINTER_CONFIG_TYPE_DAEMON (printer_config_daemon_get_type ()) +#define PRINTER_CONFIG_DAEMON(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + PRINTER_CONFIG_TYPE_DAEMON, \ + PrinterConfigDaemon)) +#define PRINTER_CONFIG_IS_DAEMON_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), PRINTER_CONFIG_TYPE_DAEMON, \ + PrinterConfigDaemonClass)) +#define PRINTER_CONFIG_DAEMON_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), PRINTER_CONFIG_TYPE_DAEMON, \ + PrinterConfigDaemonClass)) + +typedef struct _PrinterConfigDaemon PrinterConfigDaemon; +typedef struct _PrinterConfigDaemonClass PrinterConfigDaemonClass; + +struct children; +struct _PrinterConfigDaemon +{ + GObject parent_instance; + + /* instance members */ + struct children *children; + guint killtimer; +}; + +struct _PrinterConfigDaemonClass +{ + GObjectClass parent_class; + + /* class members */ + DBusGConnection *connection; +}; + +/* used by PRINTER_CONFIG_TYPE_DAEMON */ +GType printer_config_daemon_get_type (void); + +PrinterConfigDaemon *printer_config_daemon_new (void); +gboolean printer_config_daemon_usb_printer_add (PrinterConfigDaemon *d, + const char *devpath, + const char *deviceid, + DBusGMethodInvocation *ctx); + +gboolean printer_config_daemon_usb_printer_remove (PrinterConfigDaemon *d, + const char *devpath, + DBusGMethodInvocation *ctx); + +void main_quit (void); + +#endif /* PRINTER_CONFIG_H */ diff -up /dev/null system-config-printer-1.1.10/udev/printer-config-main.c --- /dev/null 2009-07-26 12:31:15.305032486 +0100 +++ system-config-printer-1.1.10/udev/printer-config-main.c 2009-07-26 19:01:45.702480595 +0100 @@ -0,0 +1,159 @@ +/* -*- Mode: C; c-file-style: "gnu" -*- + * + * Copyright (C) 2007 David Zeuthen + * Copyright (C) 2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DBUS_API_SUBJECT_TO_CHANGE +#include +#include + +#include "printer-config.h" + +#define NAME_TO_CLAIM "com.redhat.PrinterConfig" + +static gboolean +acquire_name_on_proxy (DBusGProxy *bus_proxy) +{ + GError *error; + guint result; + gboolean res; + gboolean ret; + + ret = FALSE; + + if (bus_proxy == NULL) { + goto out; + } + + error = NULL; + res = dbus_g_proxy_call (bus_proxy, + "RequestName", + &error, + G_TYPE_STRING, NAME_TO_CLAIM, + G_TYPE_UINT, 0, + G_TYPE_INVALID, + G_TYPE_UINT, &result, + G_TYPE_INVALID); + if (! res) { + if (error != NULL) { + g_warning ("Failed to acquire %s: %s", NAME_TO_CLAIM, error->message); + g_error_free (error); + } else { + g_warning ("Failed to acquire %s", NAME_TO_CLAIM); + } + goto out; + } + + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + if (error != NULL) { + g_warning ("Failed to acquire %s: %s", NAME_TO_CLAIM, error->message); + g_error_free (error); + } else { + g_warning ("Failed to acquire %s", NAME_TO_CLAIM); + } + goto out; + } + + ret = TRUE; + + out: + return ret; +} + +static GMainLoop *loop; + +void main_quit (void); + +void +main_quit (void) +{ + g_main_loop_quit (loop); +} + +int +main (int argc, char **argv) +{ + PrinterConfigDaemon *daemon; + GError *error; + DBusGProxy *bus_proxy; + DBusGConnection *bus; + int ret = 1; + + g_type_init (); + + error = NULL; + bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (bus == NULL) { + g_warning ("Couldn't connect to system bus: %s", error->message); + g_error_free (error); + goto out; + } + + bus_proxy = dbus_g_proxy_new_for_name (bus, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + if (bus_proxy == NULL) { + g_warning ("Could not construct bus_proxy object; bailing out"); + goto out; + } + + if (!acquire_name_on_proxy (bus_proxy) ) { + g_warning ("Could not acquire name; bailing out"); + goto out; + } + + g_debug ("Starting printer-config-daemon version %s", VERSION); + + daemon = printer_config_daemon_new (); + + if (daemon == NULL) { + goto out; + } + + loop = g_main_loop_new (NULL, FALSE); + + g_main_loop_run (loop); + + g_object_unref (daemon); + g_main_loop_unref (loop); + ret = 0; + + out: + return ret; +} diff -up system-config-printer-1.1.10/udev/udev-add-printer.525e996 system-config-printer-1.1.10/udev/udev-add-printer --- system-config-printer-1.1.10/udev/udev-add-printer.525e996 2009-07-22 13:22:49.000000000 +0100 +++ system-config-printer-1.1.10/udev/udev-add-printer 2009-07-26 19:01:45.703480670 +0100 @@ -27,17 +27,28 @@ import sys import traceback from syslog import * -def create_queue (c, name, device_uri, ppdname, info): +def create_queue (c, printers, name, device_uri, ppdname, info): # Make sure the name is unique. - printers = cupshelpers.getPrinters (c) - names = printers.keys () - if name in names: - suffix = 2 - while (name + "-" + str (suffix)) in names: + namel = unicode (name.lower ()) + unique = False + suffix = 1 + while not unique: + unique = True + for printer in printers.values (): + if (not printer.discovered and + ((suffix == 1 and printer.name.lower () == namel) or + (suffix > 1 and + printer.name.lower () == namel + "-" + str (suffix)))): + unique = False + break + + if not unique: suffix += 1 if suffix == 100: break - name += str (suffix) + + if suffix > 1: + name += "-" + str (suffix) c.addPrinter (name, device=device_uri, @@ -45,14 +56,19 @@ def create_queue (c, name, device_uri, p info=info, location=os.uname ()[1]) cupshelpers.activateNewPrinter (c, name) + return name -def add_queue (device_id, device_uris, is_fax=False): +def add_queue (device_id, device_uris, fax_basename=False): """ Create a CUPS queue. + + device_id: the IEEE 1284 Device ID of the device to add a queue for. + device_uris: device URIs, best first, for this device + fax_basename: False if this is not a fax queue, else name prefix """ syslog (LOG_DEBUG, "add_queue: URIs=%s" % device_uris) - if is_fax: + if fax_basename != False: notification = None else: try: @@ -83,34 +99,22 @@ def add_queue (device_id, device_uris, i name = name.replace ("/", "-") name = name.replace ("#", "-") - create_queue (c, name, device_uris[0], ppdname, - "%s %s" % (id_dict["MFG"], id_dict["MDL"])) + if fax_basename != False: + name = fax_basename + "-" + name - if not is_fax: + printers = cupshelpers.getPrinters (c) + uniquename = create_queue (c, printers, name, device_uris[0], ppdname, + "%s %s" % (id_dict["MFG"], id_dict["MDL"])) + + if fax_basename == False: # Look for a corresponding fax queue. We can only # identify these by looking for device URIs that are the # same as this one but with a different scheme. If we # find one whose scheme ends in "fax", use that as a fax # queue. Note that the HPLIP backends do follow this # pattern (hp and hpfax). - not_fax_schemes=["beh", - "bluetooth", - "http", - "https", - "ipp", - "lpd", - "parallel", - "serial", - "smb", - "snmp", - "socket", - "scsi", - "usb"] - devices = c.getDevices (exclude_schemes=not_fax_schemes) - for uri, device_dict in devices.iteritems (): - if uri in device_uris: - continue - + used_uris = map (lambda x: x.device_uri, printers.values ()) + for uri in device_uris[1:]: if uri.find (":") == -1: continue @@ -119,17 +123,34 @@ def add_queue (device_id, device_uris, i # Now see if the non-scheme parts of the URI match # any of the URIs we were given. for each_uri in device_uris: + if each_uri == uri: + continue (s, device_uri_rest) = each_uri.split (":", 1) if rest == device_uri_rest: + # This one matches. Check there is not + # already a queue using this URI. + if uri in used_uris: + break + + try: + devices = c.getDevices(include_schemes=[scheme]) + except TypeError: + # include_schemes requires pycups 1.9.46 + devices = c.getDevices () + + device_dict = devices.get (uri) + if device_dict == None: + break + add_queue (device_dict.get ("device-id", ""), - [uri], is_fax=True) + [uri], fax_basename=uniquename) else: # Not an exact match. - name = device_uris[0] + uniquename = device_uris[0] if notification: try: - notification.NewPrinter (status, name, id_dict["MFG"], + notification.NewPrinter (status, uniquename, id_dict["MFG"], id_dict["MDL"], id_dict["DES"], reduce(lambda x, y: x + ',' + y, id_dict["CMD"])) diff -up system-config-printer-1.1.10/udev/udev-configure-printer.c.525e996 system-config-printer-1.1.10/udev/udev-configure-printer.c diff -up /dev/null system-config-printer-1.1.10/udev/udev-usb-printer.c --- /dev/null 2009-07-26 12:31:15.305032486 +0100 +++ system-config-printer-1.1.10/udev/udev-usb-printer.c 2009-07-26 19:01:45.711481093 +0100 @@ -0,0 +1,454 @@ +/* -*- Mode: C; c-file-style: "gnu" -*- + * udev-usb-printer - a udev callout to configure print queues + * Copyright (C) 2009 Red Hat, Inc. + * Author: Tim Waugh + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * The protocol for this program is: + * + * udev-usb-printer add {DEVPATH} + * udev-usb-printer remove {DEVPATH} + * + * where DEVPATH is the path (%p) of the device + */ + +#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "printer-config-client-bindings.h" + +struct device_id +{ + char *full_device_id; + char *mfg; + char *mdl; + char *sern; +}; + +static void +free_device_id (struct device_id *id) +{ + free (id->full_device_id); + free (id->mfg); + free (id->mdl); + free (id->sern); +} + +static void +parse_device_id (const char *device_id, + struct device_id *id) +{ + char *fieldname; + char *start, *end; + size_t len; + + len = strlen (device_id); + if (len == 0) + return; + + if (device_id[len - 1] == '\n') + len--; + + id->full_device_id = malloc (len + 1); + fieldname = malloc (len + 1); + if (!id->full_device_id || !fieldname) + { + syslog (LOG_ERR, "out of memory"); + exit (1); + } + + memcpy (id->full_device_id, device_id, len); + id->full_device_id[len] = '\0'; + fieldname[0] = '\0'; + start = id->full_device_id; + while (*start != '\0') + { + /* New field. */ + + end = start; + while (*end != '\0' && *end != ':') + end++; + + if (*end == '\0') + break; + + len = end - start; + memcpy (fieldname, start, len); + fieldname[len] = '\0'; + + start = end + 1; + while (*end != '\0' && *end != ';') + end++; + + len = end - start; + + if (!id->mfg && + (!strncasecmp (fieldname, "MANUFACTURER", 12) || + !strncasecmp (fieldname, "MFG", 3))) + id->mfg = strndup (start, len); + else if (!id->mdl && + (!strncasecmp (fieldname, "MODEL", 5) || + !strncasecmp (fieldname, "MDL", 3))) + id->mdl = strndup (start, len); + else if (!id->sern && + (!strncasecmp (fieldname, "SERIALNUMBER", 12) || + !strncasecmp (fieldname, "SERN", 4) || + !strncasecmp (fieldname, "SN", 2))) + id->sern = strndup (start, len); + + if (*end != '\0') + start = end + 1; + } + + free (fieldname); +} + +static char * +syspath_from_devpath (struct udev *udev, const char *devpath) +{ + const char *sys; + char *syspath; + size_t syslen, devpathlen = strlen (devpath); + sys = udev_get_sys_path (udev); + syslen = strlen (sys); + syspath = malloc (syslen + devpathlen + 1); + if (syspath == NULL) + return NULL; + + memcpy (syspath, sys, syslen); + memcpy (syspath + syslen, devpath, devpathlen); + syspath[syslen + devpathlen] = '\0'; + return syspath; +} + +static char * +device_id_from_devpath (const char *devpath, + struct device_id *id, + char *usbserial, size_t usbseriallen) +{ + struct udev *udev; + struct udev_device *dev, *parent_dev = NULL; + const char *idVendorStr, *idProductStr, *serial; + char *end; + unsigned long idVendor, idProduct; + struct usb_bus *bus; + struct usb_dev_handle *handle = NULL; + char ieee1284_id[1024]; + const char *device_id = NULL; + char *syspath; + int conf = 0, iface = 0; + int got = 0; + char *usb_device_devpath; + + id->full_device_id = id->mfg = id->mdl = id->sern = NULL; + + udev = udev_new (); + if (udev == NULL) + { + syslog (LOG_ERR, "udev_new failed"); + exit (1); + } + + syspath = syspath_from_devpath (udev, devpath); + if (syspath == NULL) + { + udev_unref (udev); + exit (1); + } + + dev = udev_device_new_from_syspath (udev, syspath); + if (dev == NULL) + { + udev_device_unref (dev); + udev_unref (udev); + syslog (LOG_ERR, "unable to access %s", syspath); + free (syspath); + exit (1); + } + + free (syspath); + parent_dev = udev_device_get_parent_with_subsystem_devtype (dev, + "usb", + "usb_device"); + if (!parent_dev) + { + udev_unref (udev); + syslog (LOG_ERR, "Failed to get parent"); + exit (1); + } + + usb_device_devpath = strdup (udev_device_get_devpath (parent_dev)); + syslog (LOG_DEBUG, "parent devpath is %s", usb_device_devpath); + + serial = udev_device_get_sysattr_value (parent_dev, "serial"); + if (serial) + { + strncpy (usbserial, serial, usbseriallen); + usbserial[usbseriallen - 1] = '\0'; + } + else + usbserial[0] = '\0'; + + /* See if we were triggered by a usblp add event. */ + device_id = udev_device_get_sysattr_value (dev, "device/ieee1284_id"); + if (device_id) + { + got = 1; + goto got_deviceid; + } + + /* This is a low-level USB device. Use libusb to fetch the Device ID. */ + idVendorStr = udev_device_get_sysattr_value (parent_dev, "idVendor"); + idProductStr = udev_device_get_sysattr_value (parent_dev, "idProduct"); + + if (!idVendorStr || !idProductStr) + { + udev_device_unref (dev); + udev_unref (udev); + syslog (LOG_ERR, "Missing sysattr %s", + idVendorStr ? + (idProductStr ? "serial" : "idProduct") : "idVendor"); + exit (1); + } + + idVendor = strtoul (idVendorStr, &end, 16); + if (end == idVendorStr) + { + syslog (LOG_ERR, "Invalid idVendor: %s", idVendorStr); + exit (1); + } + + idProduct = strtoul (idProductStr, &end, 16); + if (end == idProductStr) + { + syslog (LOG_ERR, "Invalid idProduct: %s", idProductStr); + exit (1); + } + + syslog (LOG_DEBUG, "Device vendor/product is %04zX:%04zX", + idVendor, idProduct); + + usb_init (); + usb_find_busses (); + usb_find_devices (); + for (bus = usb_get_busses (); bus && !got; bus = bus->next) + { + struct usb_device *device; + for (device = bus->devices; device && !got; device = device->next) + { + struct usb_config_descriptor *confptr; + if (device->descriptor.idVendor != idVendor || + device->descriptor.idProduct != idProduct || + !device->config) + continue; + + conf = 0; + for (confptr = device->config; + conf < device->descriptor.bNumConfigurations && !got; + conf++, confptr++) + { + struct usb_interface *ifaceptr; + iface = 0; + for (ifaceptr = confptr->interface; + iface < confptr->bNumInterfaces && !got; + iface++, ifaceptr++) + { + struct usb_interface_descriptor *altptr; + int altset = 0; + for (altptr = ifaceptr->altsetting; + altset < ifaceptr->num_altsetting && !got; + altset++, altptr++) + { + if (altptr->bInterfaceClass == USB_CLASS_PRINTER && + altptr->bInterfaceSubClass == 1) + { + int n; + handle = usb_open (device); + if (!handle) + { + syslog (LOG_DEBUG, "failed to open device"); + continue; + } + + n = altptr->bInterfaceNumber; + if (usb_claim_interface (handle, n) < 0) + { + usb_close (handle); + handle = NULL; + syslog (LOG_DEBUG, "failed to claim interface"); + continue; + } + + if (n != 0 && usb_claim_interface (handle, 0) < 0) + { + usb_close (handle); + handle = NULL; + syslog (LOG_DEBUG, "failed to claim interface 0"); + continue; + } + + n = altptr->bAlternateSetting; + if (usb_set_altinterface (handle, n) < 0) + { + usb_close (handle); + handle = NULL; + syslog (LOG_DEBUG, "failed set altinterface"); + continue; + } + + memset (ieee1284_id, '\0', sizeof (ieee1284_id)); + if (usb_control_msg (handle, + USB_TYPE_CLASS | + USB_ENDPOINT_IN | + USB_RECIP_INTERFACE, + 0, conf, iface, + ieee1284_id, + sizeof (ieee1284_id), + 5000) < 0) + { + usb_close (handle); + handle = NULL; + syslog (LOG_ERR, "Failed to fetch Device ID"); + continue; + } + + got = 1; + usb_close (handle); + break; + } + } + } + } + } + } + + got_deviceid: + if (got) + { + if (!device_id) + device_id = ieee1284_id + 2; + parse_device_id (device_id, id); + } + + udev_device_unref (dev); + udev_unref (udev); + return usb_device_devpath; +} + +static int +do_add (DBusGProxy *proxy, const char *devpath) +{ + GError *error = NULL; + struct device_id id; + char *usb_device_devpath; + char usbserial[256]; + + syslog (LOG_DEBUG, "add %s", devpath); + + usb_device_devpath = device_id_from_devpath (devpath, &id, + usbserial, sizeof (usbserial)); + + if (!id.mfg || !id.mdl) + { + syslog (LOG_ERR, "invalid or missing IEEE 1284 Device ID%s%s", + id.full_device_id ? " " : "", + id.full_device_id ? id.full_device_id : ""); + exit (1); + } + + syslog (LOG_DEBUG, "MFG:%s MDL:%s SERN:%s serial:%s", id.mfg, id.mdl, + id.sern ? id.sern : "-", usbserial); + + com_redhat_PrinterConfig_usb_printer_add (proxy, + usb_device_devpath, + id.full_device_id, + &error); + free (usb_device_devpath); + free_device_id (&id); + return 0; +} + +static int +do_remove (DBusGProxy *proxy, const char *devpath) +{ + GError *error = NULL; + syslog (LOG_DEBUG, "remove %s", devpath); + + com_redhat_PrinterConfig_usb_printer_remove (proxy, + devpath, + &error); + return 0; +} + +int +main (int argc, char **argv) +{ + DBusGConnection *connection; + DBusGProxy *proxy; + GError *error = NULL; + int add; + + g_type_init (); + + if (argc != 3 || + !((add = !strcmp (argv[1], "add")) || + !strcmp (argv[1], "remove"))) + { + fprintf (stderr, + "Syntax: %s add {USB device path}\n" + " %s remove {USB device path}\n", + argv[0], argv[0]); + return 1; + } + + openlog ("udev-usb-printer", 0, LOG_LPR); + + connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (connection == NULL) + { + syslog (LOG_ERR, "unable to connect to D-Bus: %s", error->message); + g_error_free (error); + exit (1); + } + + proxy = dbus_g_proxy_new_for_name (connection, + "com.redhat.PrinterConfig", + "/com/redhat/PrinterConfig", + "com.redhat.PrinterConfig"); + + if (add) + do_add (proxy, argv[2]); + else + do_remove (proxy, argv[2]); + + g_object_unref (proxy); + return 0; +}