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.