2 * "$Id: serial.c,v 1.32.2.18 2004/06/29 13:15:08 mike Exp $"
4 * Serial port backend for the Common UNIX Printing System (CUPS).
6 * Copyright 1997-2004 by Easy Software Products, all rights reserved.
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636-3142 USA
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
24 * This file is subject to the Apple OS-Developed Software exception.
28 * main() - Send a file to the printer or server.
29 * list_devices() - List all serial devices.
33 * Include necessary headers.
36 #include <cups/cups.h>
40 #include <cups/string.h>
44 # include <sys/modem.h>
53 # ifdef HAVE_SYS_IOCTL_H
54 # include <sys/ioctl.h>
55 # endif /* HAVE_SYS_IOCTL_H */
60 # ifndef INV_EPP_ECP_PLP
61 # define INV_EPP_ECP_PLP 6 /* From 6.3/6.4/6.5 sys/invent.h */
62 # define INV_ASO_SERIAL 14 /* serial portion of SGI ASO board */
63 # define INV_IOC3_DMA 16 /* DMA mode IOC3 serial */
64 # define INV_IOC3_PIO 17 /* PIO mode IOC3 serial */
65 # define INV_ISA_DMA 19 /* DMA mode ISA serial -- O2 */
66 # endif /* !INV_EPP_ECP_PLP */
71 # define CRTSCTS CNEW_RTSCTS
74 # endif /* CNEW_RTSCTS */
77 #if defined(__APPLE__)
78 # include <CoreFoundation/CoreFoundation.h>
79 # include <IOKit/IOKitLib.h>
80 # include <IOKit/serial/IOSerialKeys.h>
81 # include <IOKit/IOBSD.h>
82 #endif /* __APPLE__ */
89 void list_devices(void);
93 * 'main()' - Send a file to the printer or server.
97 * printer-uri job-id user title copies options [file]
100 int /* O - Exit status */
101 main(int argc
, /* I - Number of command-line arguments (6 or 7) */
102 char *argv
[]) /* I - Command-line arguments */
104 char method
[255], /* Method in URI */
105 hostname
[1024], /* Hostname */
106 username
[255], /* Username info (not used) */
107 resource
[1024], /* Resource info (device and options) */
108 *options
, /* Pointer to options */
109 name
[255], /* Name of option */
110 value
[255], /* Value of option */
111 *ptr
; /* Pointer into name or value */
112 int port
; /* Port number (not used) */
113 int fp
; /* Print file */
114 int copies
; /* Number of copies to print */
115 int fd
; /* Parallel device */
116 int wbytes
; /* Number of bytes written */
117 size_t nbytes
, /* Number of bytes read */
118 tbytes
; /* Total number of bytes written */
119 int dtrdsr
; /* Do dtr/dsr flow control? */
120 int bufsize
; /* Size of output buffer for writes */
121 char buffer
[8192], /* Output buffer */
122 *bufptr
; /* Pointer into buffer */
123 struct termios opts
; /* Serial port options */
124 struct termios origopts
; /* Original port options */
125 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
126 struct sigaction action
; /* Actions for POSIX signals */
127 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
131 * Make sure status messages are not buffered...
134 setbuf(stderr
, NULL
);
137 * Ignore SIGPIPE signals...
141 sigset(SIGPIPE
, SIG_IGN
);
142 #elif defined(HAVE_SIGACTION)
143 memset(&action
, 0, sizeof(action
));
144 action
.sa_handler
= SIG_IGN
;
145 sigaction(SIGPIPE
, &action
, NULL
);
147 signal(SIGPIPE
, SIG_IGN
);
148 #endif /* HAVE_SIGSET */
151 * Check command-line...
159 else if (argc
< 6 || argc
> 7)
161 fputs("Usage: serial job-id user title copies options [file]\n", stderr
);
166 * If we have 7 arguments, print the file named on the command-line.
167 * Otherwise, send stdin instead...
178 * Try to open the print file...
181 if ((fp
= open(argv
[6], O_RDONLY
)) < 0)
183 perror("ERROR: unable to open print file");
187 copies
= atoi(argv
[4]);
191 * Extract the device name and options from the URI...
194 httpSeparate(argv
[0], method
, username
, hostname
, &port
, resource
);
197 * See if there are any options...
200 if ((options
= strchr(resource
, '?')) != NULL
)
203 * Yup, terminate the device name string and move to the first
204 * character of the options...
211 * Open the serial port device...
216 if ((fd
= open(resource
, O_WRONLY
| O_NOCTTY
| O_EXCL
| O_NDELAY
)) == -1)
220 fputs("INFO: Serial port busy; will retry in 30 seconds...\n", stderr
);
225 fprintf(stderr
, "ERROR: Unable to open serial port device file \"%s\": %s\n",
226 resource
, strerror(errno
));
234 * Set any options provided...
237 tcgetattr(fd
, &origopts
);
238 tcgetattr(fd
, &opts
);
240 opts
.c_lflag
&= ~(ICANON
| ECHO
| ISIG
); /* Raw mode */
241 opts
.c_oflag
&= ~OPOST
; /* Don't post-process */
243 bufsize
= 96; /* 9600 baud / 10 bits/char / 10Hz */
244 dtrdsr
= 0; /* No dtr/dsr flow control */
253 for (ptr
= name
; *options
&& *options
!= '=';)
254 if (ptr
< (name
+ sizeof(name
) - 1))
266 for (ptr
= value
; *options
&& *options
!= '+';)
267 if (ptr
< (value
+ sizeof(value
) - 1))
278 * Process the option...
281 if (strcasecmp(name
, "baud") == 0)
284 * Set the baud rate...
287 bufsize
= atoi(value
) / 100;
290 cfsetispeed(&opts
, atoi(value
));
291 cfsetospeed(&opts
, atoi(value
));
296 cfsetispeed(&opts
, B1200
);
297 cfsetospeed(&opts
, B1200
);
300 cfsetispeed(&opts
, B2400
);
301 cfsetospeed(&opts
, B2400
);
304 cfsetispeed(&opts
, B4800
);
305 cfsetospeed(&opts
, B4800
);
308 cfsetispeed(&opts
, B9600
);
309 cfsetospeed(&opts
, B9600
);
312 cfsetispeed(&opts
, B19200
);
313 cfsetospeed(&opts
, B19200
);
316 cfsetispeed(&opts
, B38400
);
317 cfsetospeed(&opts
, B38400
);
321 cfsetispeed(&opts
, B57600
);
322 cfsetospeed(&opts
, B57600
);
327 cfsetispeed(&opts
, B115200
);
328 cfsetospeed(&opts
, B115200
);
330 # endif /* B115200 */
333 cfsetispeed(&opts
, B230400
);
334 cfsetospeed(&opts
, B230400
);
336 # endif /* B230400 */
338 fprintf(stderr
, "WARNING: Unsupported baud rate %s!\n", value
);
341 #endif /* B19200 == 19200 */
343 else if (strcasecmp(name
, "bits") == 0)
346 * Set number of data bits...
352 opts
.c_cflag
&= ~CSIZE
;
354 opts
.c_cflag
|= PARENB
;
355 opts
.c_cflag
&= ~PARODD
;
358 opts
.c_cflag
&= ~CSIZE
;
360 opts
.c_cflag
&= ~PARENB
;
364 else if (strcasecmp(name
, "parity") == 0)
367 * Set parity checking...
370 if (strcasecmp(value
, "even") == 0)
372 opts
.c_cflag
|= PARENB
;
373 opts
.c_cflag
&= ~PARODD
;
375 else if (strcasecmp(value
, "odd") == 0)
377 opts
.c_cflag
|= PARENB
;
378 opts
.c_cflag
|= PARODD
;
380 else if (strcasecmp(value
, "none") == 0)
381 opts
.c_cflag
&= ~PARENB
;
383 else if (strcasecmp(name
, "flow") == 0)
386 * Set flow control...
389 if (strcasecmp(value
, "none") == 0)
391 opts
.c_iflag
&= ~(IXON
| IXOFF
);
392 opts
.c_cflag
&= ~CRTSCTS
;
394 else if (strcasecmp(value
, "soft") == 0)
396 opts
.c_iflag
|= IXON
| IXOFF
;
397 opts
.c_cflag
&= ~CRTSCTS
;
399 else if (strcasecmp(value
, "hard") == 0 ||
400 strcasecmp(value
, "rtscts") == 0)
402 opts
.c_iflag
&= ~(IXON
| IXOFF
);
403 opts
.c_cflag
|= CRTSCTS
;
405 else if (strcasecmp(value
, "dtrdsr") == 0)
407 opts
.c_iflag
&= ~(IXON
| IXOFF
);
408 opts
.c_cflag
&= ~CRTSCTS
;
415 tcsetattr(fd
, TCSANOW
, &opts
);
416 fcntl(fd
, F_SETFL
, 0);
419 * Now that we are "connected" to the port, ignore SIGTERM so that we
420 * can finish out any page data the driver sends (e.g. to eject the
421 * current page... Only ignore SIGTERM if we are printing data from
422 * stdin (otherwise you can't cancel raw jobs...)
427 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
428 sigset(SIGTERM
, SIG_IGN
);
429 #elif defined(HAVE_SIGACTION)
430 memset(&action
, 0, sizeof(action
));
432 sigemptyset(&action
.sa_mask
);
433 action
.sa_handler
= SIG_IGN
;
434 sigaction(SIGTERM
, &action
, NULL
);
436 signal(SIGTERM
, SIG_IGN
);
437 #endif /* HAVE_SIGSET */
441 * Finally, send the print file...
444 if (bufsize
> sizeof(buffer
))
445 bufsize
= sizeof(buffer
);
455 fputs("PAGE: 1 1\n", stderr
);
456 lseek(fp
, 0, SEEK_SET
);
462 * Check the port and sleep until DSR is set...
468 if (!ioctl(fd
, TIOCMGET
, &status
))
469 if (!(status
& TIOCM_DSR
))
472 * Wait for DSR to go high...
475 fputs("DEBUG: DSR is low; waiting for device...\n", stderr
);
480 if (ioctl(fd
, TIOCMGET
, &status
))
483 while (!(status
& TIOCM_DSR
));
485 fputs("DEBUG: DSR is high; writing to device...\n", stderr
);
490 while ((nbytes
= read(fp
, buffer
, bufsize
)) > 0)
493 * Write the print data to the printer...
501 if ((wbytes
= write(fd
, bufptr
, nbytes
)) < 0)
503 wbytes
= write(fd
, bufptr
, nbytes
);
507 perror("ERROR: Unable to send print file to printer");
519 fprintf(stderr
, "INFO: Sending print file, %lu bytes...\n",
520 (unsigned long)tbytes
);
525 * Close the serial port and input file and return...
528 tcsetattr(fd
, TCSADRAIN
, &origopts
);
539 * 'list_devices()' - List all serial devices.
545 #if defined(__hpux) || defined(__sgi) || defined(__sun) || defined(__FreeBSD__) || defined(__OpenBSD__)
546 static char *funky_hex
= "0123456789abcdefghijklmnopqrstuvwxyz";
547 /* Funky hex numbering used for some devices */
548 #endif /* __hpux || __sgi || __sun || __FreeBSD__ || __OpenBSD__ */
550 #if defined(__linux) || defined(linux) || defined(__linux__)
551 int i
; /* Looping var */
552 int fd
; /* File descriptor */
553 char device
[255]; /* Device filename */
556 for (i
= 0; i
< 100; i
++)
558 sprintf(device
, "/dev/ttyS%d", i
);
559 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
562 # if defined(_ARCH_PPC) || defined(powerpc) || defined(__powerpc)
563 printf("serial serial:%s?baud=230400 \"Unknown\" \"Serial Port #%d\"\n",
566 printf("serial serial:%s?baud=115200 \"Unknown\" \"Serial Port #%d\"\n",
568 # endif /* _ARCH_PPC || powerpc || __powerpc */
572 for (i
= 0; i
< 16; i
++)
574 sprintf(device
, "/dev/usb/ttyUSB%d", i
);
575 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
578 printf("serial serial:%s?baud=230400 \"Unknown\" \"USB Serial Port #%d\"\n",
583 int i
, j
, n
; /* Looping vars */
584 char device
[255]; /* Device filename */
585 inventory_t
*inv
; /* Hardware inventory info */
589 * IRIX maintains a hardware inventory of most devices...
594 while ((inv
= getinvent()) != NULL
)
596 if (inv
->inv_class
== INV_SERIAL
)
599 * Some sort of serial port...
602 if (inv
->inv_type
== INV_CDSIO
|| inv
->inv_type
== INV_CDSIO_E
)
608 for (n
= 0; n
< 6; n
++)
609 printf("serial serial:/dev/ttyd%d?baud=38400 \"Unknown\" \"CDSIO Board %d Serial Port #%d\"\n",
610 n
+ 5 + 8 * inv
->inv_controller
, inv
->inv_controller
, n
+ 1);
612 else if (inv
->inv_type
== INV_EPC_SERIAL
)
615 * Everest serial port...
618 if (inv
->inv_unit
== 0)
621 i
= 41 + 4 * (int)inv
->inv_controller
;
623 for (n
= 0; n
< (int)inv
->inv_state
; n
++)
624 printf("serial serial:/dev/ttyd%d?baud=38400 \"Unknown\" \"EPC Serial Port %d, Ebus slot %d\"\n",
625 n
+ i
, n
+ 1, (int)inv
->inv_controller
);
627 else if (inv
->inv_state
> 1)
630 * Standard serial port under IRIX 6.4 and earlier...
633 for (n
= 0; n
< (int)inv
->inv_state
; n
++)
634 printf("serial serial:/dev/ttyd%d?baud=38400 \"Unknown\" \"Onboard Serial Port %d\"\n",
635 n
+ (int)inv
->inv_unit
+ 1, n
+ (int)inv
->inv_unit
+ 1);
640 * Standard serial port under IRIX 6.5 and beyond...
643 printf("serial serial:/dev/ttyd%d?baud=115200 \"Unknown\" \"Onboard Serial Port %d\"\n",
644 (int)inv
->inv_controller
, (int)inv
->inv_controller
);
652 * Central Data makes serial and parallel "servers" that can be
653 * connected in a number of ways. Look for ports...
656 for (i
= 0; i
< 10; i
++)
657 for (j
= 0; j
< 8; j
++)
658 for (n
= 0; n
< 32; n
++)
660 if (i
== 8) /* EtherLite */
661 sprintf(device
, "/dev/ttydn%d%c", j
, funky_hex
[n
]);
662 else if (i
== 9) /* PCI */
663 sprintf(device
, "/dev/ttydp%d%c", j
, funky_hex
[n
]);
665 sprintf(device
, "/dev/ttyd%d%d%c", i
, j
, funky_hex
[n
]);
667 if (access(device
, 0) == 0)
670 printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data EtherLite Serial Port, ID %d, port %d\"\n",
673 printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data PCI Serial Port, ID %d, port %d\"\n",
676 printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data SCSI Serial Port, logical bus %d, ID %d, port %d\"\n",
681 int i
, j
, n
; /* Looping vars */
682 char device
[255]; /* Device filename */
686 * Standard serial ports...
689 for (i
= 0; i
< 26; i
++)
691 sprintf(device
, "/dev/cua/%c", 'a' + i
);
692 if (access(device
, 0) == 0)
694 printf("serial serial:%s?baud=115200 \"Unknown\" \"Serial Port #%d\"\n",
697 printf("serial serial:%s?baud=38400 \"Unknown\" \"Serial Port #%d\"\n",
703 * MAGMA serial ports...
706 for (i
= 0; i
< 40; i
++)
708 sprintf(device
, "/dev/term/%02d", i
);
709 if (access(device
, 0) == 0)
710 printf("serial serial:%s?baud=38400 \"Unknown\" \"MAGMA Serial Board #%d Port #%d\"\n",
711 device
, (i
/ 10) + 1, (i
% 10) + 1);
715 * Central Data serial ports...
718 for (i
= 0; i
< 9; i
++)
719 for (j
= 0; j
< 8; j
++)
720 for (n
= 0; n
< 32; n
++)
722 if (i
== 8) /* EtherLite */
723 sprintf(device
, "/dev/sts/ttyN%d%c", j
, funky_hex
[n
]);
725 sprintf(device
, "/dev/sts/tty%c%d%c", i
+ 'C', j
,
728 if (access(device
, 0) == 0)
731 printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data EtherLite Serial Port, ID %d, port %d\"\n",
734 printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data SCSI Serial Port, logical bus %d, ID %d, port %d\"\n",
738 #elif defined(__hpux)
739 int i
, j
, n
; /* Looping vars */
740 char device
[255]; /* Device filename */
744 * Standard serial ports...
747 for (i
= 0; i
< 10; i
++)
749 sprintf(device
, "/dev/tty%dp0", i
);
750 if (access(device
, 0) == 0)
751 printf("serial serial:%s?baud=38400 \"Unknown\" \"Serial Port #%d\"\n",
756 * Central Data serial ports...
759 for (i
= 0; i
< 9; i
++)
760 for (j
= 0; j
< 8; j
++)
761 for (n
= 0; n
< 32; n
++)
763 if (i
== 8) /* EtherLite */
764 sprintf(device
, "/dev/ttyN%d%c", j
, funky_hex
[n
]);
766 sprintf(device
, "/dev/tty%c%d%c", i
+ 'C', j
,
769 if (access(device
, 0) == 0)
772 printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data EtherLite Serial Port, ID %d, port %d\"\n",
775 printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data SCSI Serial Port, logical bus %d, ID %d, port %d\"\n",
779 #elif defined(__osf__)
780 int i
; /* Looping var */
781 char device
[255]; /* Device filename */
785 * Standard serial ports...
788 for (i
= 0; i
< 100; i
++)
790 sprintf(device
, "/dev/tty%02d", i
);
791 if (access(device
, 0) == 0)
792 printf("serial serial:%s?baud=38400 \"Unknown\" \"Serial Port #%d\"\n",
795 #elif defined(__FreeBSD__) || defined(__OpenBSD__)
796 int i
, j
; /* Looping vars */
797 int fd
; /* File descriptor */
798 char device
[255]; /* Device filename */
805 for (i
= 0; i
< 32; i
++)
807 sprintf(device
, "/dev/ttyd%c", funky_hex
[i
]);
808 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
811 printf("serial serial:%s?baud=115200 \"Unknown\" \"Standard Serial Port #%d\"\n",
820 for (i
= 0; i
< 16; i
++) /* Should be up to 65536 boards... */
821 for (j
= 0; j
< 32; j
++)
823 sprintf(device
, "/dev/ttyc%d%c", i
, funky_hex
[j
]);
824 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
827 printf("serial serial:%s?baud=115200 \"Unknown\" \"Cyclades #%d Serial Port #%d\"\n",
836 for (i
= 0; i
< 16; i
++) /* Should be up to 65536 boards... */
837 for (j
= 0; j
< 32; j
++)
839 sprintf(device
, "/dev/ttyD%d%c", i
, funky_hex
[j
]);
840 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
843 printf("serial serial:%s?baud=115200 \"Unknown\" \"Digiboard #%d Serial Port #%d\"\n",
852 for (i
= 0; i
< 32; i
++)
854 sprintf(device
, "/dev/ttyE%c", funky_hex
[i
]);
855 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
858 printf("serial serial:%s?baud=115200 \"Unknown\" \"Stallion Serial Port #%d\"\n",
867 for (i
= 0; i
< 128; i
++)
869 sprintf(device
, "/dev/ttyA%d", i
+ 1);
870 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
873 printf("serial serial:%s?baud=115200 \"Unknown\" \"SX Serial Port #%d\"\n",
877 #elif defined(__NetBSD__)
878 int i
, j
; /* Looping vars */
879 int fd
; /* File descriptor */
880 char device
[255]; /* Device filename */
884 * Standard serial ports...
887 for (i
= 0; i
< 4; i
++)
889 sprintf(device
, "/dev/tty%02d", i
);
890 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
893 printf("serial serial:%s?baud=115200 \"Unknown\" \"Serial Port #%d\"\n",
899 * Cyclades-Z ports...
902 for (i
= 0; i
< 16; i
++) /* Should be up to 65536 boards... */
903 for (j
= 0; j
< 64; j
++)
905 sprintf(device
, "/dev/ttyCZ%02d%02d", i
, j
);
906 if ((fd
= open(device
, O_WRONLY
| O_NOCTTY
| O_NDELAY
)) >= 0)
909 printf("serial serial:%s?baud=115200 \"Unknown\" \"Cyclades #%d Serial Prt #%d\"\n",
913 #elif defined(__APPLE__)
915 * Standard serial ports on MacOS X...
918 kern_return_t kernResult
;
919 mach_port_t masterPort
;
920 io_iterator_t serialPortIterator
;
921 CFMutableDictionaryRef classesToMatch
;
922 io_object_t serialService
;
924 printf("serial serial \"Unknown\" \"Serial Printer (serial)\"\n");
926 kernResult
= IOMasterPort(MACH_PORT_NULL
, &masterPort
);
927 if (KERN_SUCCESS
!= kernResult
)
931 * Serial devices are instances of class IOSerialBSDClient.
934 classesToMatch
= IOServiceMatching(kIOSerialBSDServiceValue
);
935 if (classesToMatch
!= NULL
)
937 CFDictionarySetValue(classesToMatch
, CFSTR(kIOSerialBSDTypeKey
),
938 CFSTR(kIOSerialBSDRS232Type
));
940 kernResult
= IOServiceGetMatchingServices(masterPort
, classesToMatch
,
941 &serialPortIterator
);
942 if (kernResult
== KERN_SUCCESS
)
944 while ((serialService
= IOIteratorNext(serialPortIterator
)))
946 CFTypeRef serialNameAsCFString
;
947 CFTypeRef bsdPathAsCFString
;
948 char serialName
[128];
953 serialNameAsCFString
=
954 IORegistryEntryCreateCFProperty(serialService
,
955 CFSTR(kIOTTYDeviceKey
),
956 kCFAllocatorDefault
, 0);
957 if (serialNameAsCFString
)
959 result
= CFStringGetCString(serialNameAsCFString
, serialName
,
961 kCFStringEncodingASCII
);
962 CFRelease(serialNameAsCFString
);
967 IORegistryEntryCreateCFProperty(serialService
,
968 CFSTR(kIOCalloutDeviceKey
),
969 kCFAllocatorDefault
, 0);
970 if (bsdPathAsCFString
)
972 result
= CFStringGetCString(bsdPathAsCFString
, bsdPath
,
974 kCFStringEncodingASCII
);
975 CFRelease(bsdPathAsCFString
);
978 printf("serial serial:%s?baud=115200 \"Unknown\" \"%s\"\n", bsdPath
,
984 IOObjectRelease(serialService
);
987 IOObjectRelease(serialPortIterator
); /* Release the iterator. */
995 * End of "$Id: serial.c,v 1.32.2.18 2004/06/29 13:15:08 mike Exp $".