An exercise in frustration
Or, what to do when you want to buy a printer.
Last year our trusty Lexmark Optra S 1855 (networked, extra paper tray and duplexer) got to the end of its cartridge, and we found to our annoyance that a replacement would set us back close to AUD450. Not keen to take that option, we decided to get a schmick multifunction inkjet.
It was unfortunate that I didn’t check www.linuxprinting.org first, because I bought the Canon Pixma MP620. Sure, it’s a nice printer with respectable photo and plain paper printing, good copying and scanning as well. BUT if you don’t want to run a WindowsXP/Vista/7 or Mac to be a print server, then your option is running linux and using Canon’s binary blob libraries linked in with their (imnsho) poorly written ps to Canon converter coupled with cups-bjnp, the reverse-engineered BubbleJet Network Protocol implementation.
I’m not opposed to running linux, not by any stretch. However, I really do prefer to run the OS that I actually help develop, as much as humanly possible.
With that in mind, I pulled down the source for the “linux drivers” from Canon ran a bunch of configure scripts (remembering to use gcc, and adding LDFLAGS=”-lnsl -lsocket” to the env) and got started. All proceeded reasonably well until I got to what turns out to be the actual kernel of the problem: cnijfilter. This utility is the one which takes ghostscript’s ppmraw output and turns it into Canon-specific control codes which you can then send across the wire using cups-bjnp.
To get this to compile (as distinct from linking, more on that shortly), I had to make these changes to cnijfilter/src/getipc.c:
44c44 < struct sockaddr_un sun; - > struct sockaddr_un cifsun; 55,57c55,57 < sun.sun_family = AF_UNIX; < strncpy(sun.sun_path, sname, sizeof(sun.sun_path) ); < sun.sun_path[sizeof(sun.sun_path) - 1] = ‘\0′; - > cifsun.sun_family = AF_UNIX; > strncpy(cifsun.sun_path, sname, sizeof(cifsun.sun_path) ); > cifsun.sun_path[sizeof(cifsun.sun_path) - 1] = ‘\0′; 59c59 < adrlen = sizeof(sun.sun_family) + strlen(sun.sun_path); - > adrlen = sizeof(cifsun.sun_family) + strlen(cifsun.sun_path); 61c61 < if (bind(s, (struct sockaddr *)&sun, adrlen)) - > if (bind(s, (struct sockaddr *)&cifsun, adrlen)) 67c67 < while ((c = accept(s, (struct sockaddr *)&sun, &adrlen)) >= 0) { - > while ((c = accept(s, (struct sockaddr *)&cifsun, &adrlen)) >= 0) {
which helped with some symbol name clashes (at least, it did prior to build 143).
That left me with the binary blob linking problems. Fortunately, Canon actually supplies libraries which do the real work of the translation. Unfortunately (for me), those libraries all seemed to need linux’ libc.so.6, libdl.so.2 and libpthread.so.0. There are two specific symbols which they required: __errno_location(), and stderr. Now this I could do something about.
The link phase for the binary throws up these errors:
$ /opt/SUNWspro/SS12/bin/cc -O2 -L../../319/libs_bin -o cif bjferror.o bjfilter.o bjfimage.o bjfoption.o bjfpos.o bjfrcaccess.o getipc.o bjflist.o -lcnbpcmcm319 -lcnbpess319 -lm -ldl -ltiff -lpng -lcnbpcnclapi319 -lcnbpcnclbjcmd319 -lcnbpcnclui319 -lpopt ld: warning: file libc.so.6: required by ../../319/libs_bin/libcnbpcmcm319.so, not found ld: warning: file libpthread.so.0: required by ../../319/libs_bin/libcnbpess319.so, not found ld: warning: file libdl.so.2: required by ../../319/libs_bin/libcnbpess319.so, not found Undefined first referenced symbol in file bind getipc.o accept getipc.o listen getipc.o socket getipc.o stderr ../../319/libs_bin/libcnbpcnclapi319.so __errno_location ../../319/libs_bin/libcnbpcmcm319.so ld: fatal: symbol referencing errors. No output written to cif gmake: *** [cif] Error 1
Which is annoying at best. To get past this problem, I crafted up this little file:
#include <stdio.h> int * __errno_location(void) { return (int *)NULL; } #define stdin (&_iob[0]) #define stdout (&_iob[1]) #define stderr (&_iob[2])
which I called fakery.c. I also created this mapfile to use when linking it:
$mapfile_version 2 SYMBOL_SCOPE { global: __errno_location; stderr; }; SYMBOL_VERSION GLIBC_2.3.2 {} GLIBC_2.1.3; SYMBOL_VERSION GLIBC_2.1.3 {} GLIBC_2.1; SYMBOL_VERSION GLIBC_2.1 {} GLIBC_2.0 ; SYMBOL_VERSION GLIBC_2.0 {} GLIBC_PRIVATE; SYMBOL_VERSION GLIBC_PRIVATE { local: *;};
I then compiled fakery.c using
cc -G -Kpic -M MAPFILE.libc -R/opt/local/lib/canon -o libc.so.6 fakery.c
Lo and behold, I now had a libc.so.6:
$ file libc.so.6 libc.so.6: ELF 32-bit LSB dynamic lib 80386 Version 1, dynamically linked, not stripped
with the requisite symbols available:
$ nm libc.so.6 |egrep "WEAK|GLOB" [55] | 1632| 28|FUNC |GLOB |0 |8 |__errno_location [53] | 67268| 0|OBJT |GLOB |0 |13 |_DYNAMIC [46] | 67532| 0|OBJT |GLOB |0 |16 |_edata [50] | 67536| 0|OBJT |GLOB |0 |17 |_end [52] | 1720| 0|OBJT |GLOB |0 |11 |_etext [51] | 67256| 0|OBJT |GLOB |3 |12 |_GLOBAL_OFFSET_TABLE_ [54] | 0| 0|OBJT |GLOB |0 |ABS |_PROCEDURE_LINKAGE_TABLE_ [47] | 0| 0|OBJT |WEAK |0 |ABS |GLIBC_2.0 [48] | 0| 0|OBJT |WEAK |0 |ABS |GLIBC_2.1 [49] | 0| 0|OBJT |WEAK |0 |ABS |GLIBC_2.1.3 [43] | 0| 0|OBJT |WEAK |0 |ABS |GLIBC_2.3.2 [44] | 0| 0|OBJT |GLOB |0 |ABS |GLIBC_PRIVATE [45] | 67532| 4|OBJT |GLOB |0 |17 |stderr
Yay. Now for lib_dl.c and lib_pthread.c (the same file), and their mapfile:
$mapfile_version 2 SYMBOL_VERSION GLIBC_2.3.2 {} GLIBC_2.1.3; SYMBOL_VERSION GLIBC_2.1.3 {} GLIBC_2.1; SYMBOL_VERSION GLIBC_2.1 {} GLIBC_2.0 ; SYMBOL_VERSION GLIBC_2.0 {} GLIBC_PRIVATE; SYMBOL_VERSION GLIBC_PRIVATE { local: *;};
So I could do the same trick to generate libdl.so.2 and libpthread.so.0:
cc -G -Kpic -M MAPFILE.libs -R/opt/local/lib/canon -o libdl.so.2 otherlibs.c
Then I re-ran the cnijfilter link stage again, by hand:
$ /opt/SUNWspro/SS12/bin/cc -lnsl -lsocket -O2 -L../../319/libs_bin \ -o cif bjferror.o bjfilter.o bjfimage.o bjfoption.o bjfpos.o \ bjfrcaccess.o getipc.o bjflist.o -lcnbpcmcm319 -lcnbpess319 \ -lm -ldl -ltiff -lpng -lcnbpcnclapi319 -lcnbpcnclbjcmd319 \ -lcnbpcnclui319 -lpopt **-R/opt/local/lib/canon -L../../FAKE/**
Note the last bit – I’m adding an rpath (runtime path) so the binary will
search for the libraries in /opt/local/lib/canon
, and I added the directory
for these fake libraries to the linker search path. Now the bits compile and
link, and with an LD_LIBRARY_PATH entry in my environment I can see all the
required libraries being found:
$ LD_LIBRARY_PATH=/opt/local/lib/canon ldd cif libnsl.so.1 => /lib/libnsl.so.1 libsocket.so.1 => /lib/libsocket.so.1 libcnbpcmcm319.so => /opt/local/lib/canon/libcnbpcmcm319.so libcnbpess319.so => /opt/local/lib/canon/libcnbpess319.so libm.so.2 => /lib/libm.so.2 libdl.so.1 => /lib/libdl.so.1 libtiff.so.3 => /usr/lib/libtiff.so.3 libpng12.so.0 => /usr/lib/libpng12.so.0 libcnbpcnclapi319.so => /opt/local/lib/canon/libcnbpcnclapi319.so libcnbpcnclbjcmd319.so => /opt/local/lib/canon/libcnbpcnclbjcmd319.so libcnbpcnclui319.so => /opt/local/lib/canon/libcnbpcnclui319.so libpopt.so.0 => /usr/lib/libpopt.so.0 libc.so.1 => /lib/libc.so.1 libmp.so.2 => /lib/libmp.so.2 libmd.so.1 => /lib/libmd.so.1 libc.so.6 => /opt/local/lib/canon/libc.so.6 libdl.so.2 => /opt/local/lib/canon/libdl.so.2 libpthread.so.0 => /opt/local/lib/canon/libpthread.so.0 libjpeg.so.62 => /usr/lib/libjpeg.so.62 libz.so.1 => /lib/libz.so.1
Still needed a wrapper script with it otherwise those binary blob libraries in
/opt/local/lib/canon
would fail to find libc.so.6:
#!/bin/bash LD_LIBRARY_PATH=/opt/local/lib/canon exec /opt/local/bin/cifmp610.bin $*
But otherwise it seemed to be a reasonable start. It took a few reads of the Linker and Libraries Guide, and some back-n-forth help from the linker aliens but now I had a working binary translator.
Next step – cups-bjnp, but this actually turned out to be the bit which stopped me in my tracks.
I could hack the source ok to make it compile using getifaddrs:
558a559 > #ifndef __sun 562a564,569 > #else > if ((interface->ifa_addr == NULL) || (interface->ifa_broadaddr == NULL) || > (interface->ifa_addr->ss_family != AF_INET) || > (((struct sockaddr_in *) interface->ifa_addr)->sin_addr.s_addr == > htonl(INADDR_LOOPBACK))) > #endif
But it turns out that the Solaris behaviour is different to the linux behaviour – and I just do not have the time or energy to actually go and figure out how the packets are being misinterpreted. Yes, I snooped a bunch of the network traffic, but I’m stumped.
So I’m giving up on this printer, and Canon’s “open source” linux bits (did I mention they ship under GPLv2?). Until I can get my bones together to get an Epson (tx810fw is in the linuxprinting.org fully supported list, and Epson’s source for their translator is all open), I’ll run up my archlinux vbox and print using that instead.
I’m not going to buy another Canon printer, and I strongly suggest that if you want to have a printer which works under an open source operating system (using CUPS, of course) then you check out what is listed at www.linuxprinting.org/printers before making a purchase.
I’d like to thank Rod Evans, Ali Bahrami and Enrico Perla for all their help and patience with trying to get this figured out. With their help I’ve learnt quite a bit more about our linker capabilities, and the utilities (elfedit, elfdump, lari, crle and moe amongst others) which they’ve written to make it possible for me to turn code into something that runs.