/* Copyright (C) 2001-2002 Ricardo Ueda Karpischek This is free software; you can redistribute it and/or modify it under the terms of the version 2 of the GNU General Public License as published by the Free Software Foundation. This software 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 software; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* manager.c - scanner station manager */ /* The Scanning Station -------------------- This is the program that controls the scanning station, built by the Clara OCR Project. The scanning station is a PC without console running GNU/Linux. A scanner, a 12-key serial keyboard and a CD-R drive are attached to it. Metallic extensions were added to the PC cabinet, in such a manner that the entire thing looks like one single and compact block. The main feature of this station is the ease of use. The operator just presses one key to scan and compress one page, or to burn a multi-session CD with the already scanned pages (and compare the files before removing them), or to shutdown and power off the machine. Customized speaker sounds are used to report some relevant statuses. Hardware description -------------------- Due to cost restrictions, the scanning station was built using old or cheap hardware. The keyboard is a very simple one, with 12 keys (digits 0-9, "CANCEL" and "ENTER"). The manager includes serial communication code to accept input from it. The CD-R drive is a 8x4 Mitsumi CR-4801TE CD-R. Yet the CD-R media is a bit small for such application, it's quite cheap. The PC is a P200 with 24M RAM, Adaptec 2940 controller and two old HDs: SCSI 2.1G and IDE 540M. The scanner is a Genius HR5 SCSI. The SCSI disk is used to store the scanned pages. As the IDE disk is used to store the CD image (in fact, half of the CD image, because the disk is not large enough; the CD is burned in two sessions). The SCSI HD is not used to store CD images because the HR5 scanner blocks the SCSI bus two or three times along the scanning (see the file sane-umax-config-doc.html from the SANE documentation), and in our tests we observed underruns when using the SCSI HD to store the CD image. How it works ------------ From now to the end of this document, paths are subject to changes depending on how the PATHS sections on the source code is configured. The manager is expected to be started and restarted by the init program. So a line similar to sm:2345:respawn:/usr/local/sbin/manager must be added to /etc/inittab. On startup, the manager removes the subtree /var/manager/burned, moves any files from /var/manager/toburn1 or /var/manager/toburn2 to /var/manager/toburn, removes /var/manager/toburn1 and /var/manager/toburn2, and reads configuration information from the file /var/manager/config. Unless the disk space available to non-superuser is less than 100M, when the operator presses ENTER, the manager scans one page and saves it as a new file in the directory /var/manager/new. The manager compresses files in background and saves the compressed files in the directory /var/manager/toburn. Compression is performed by 'gzip -1' because the CPU is slow. The manager stops compressing files when the contents of the directory /var/manager/toburn reaches 620M (640M is the data size limit for most CD-R medias). When the user requests a CD burn, the manager waits the current compression process (if any) to finish, forbids new compressions and calls mkisofs to masterize the filesystem and cdrecord to burn the media. On completion, it compares the media contents with the contents of /var/manager/toburn and, unless some difference is found, the directory /var/manager/toburn is renamed to /var/manager/burned and deleted subsequently. Messages are logged to /var/manager/log. This file is rotated by the manager when it reaches 1M. Three files are maintaned (log, log.1 and log.2). Operations ---------- Operations are attached to keys as follows: key | operation -------+------------------- 1 | burn CD (slow) 2 | burn CD (fast) 3 | configure scanner 4 | report CD-R status 5 | shutdown 6 | exit 7 | play all sounds 8 | disk usage ENTER | scan CANCEL | cancel current op Notes: 1. The operations 1, 2, 5 and 6 must be confirmed by a subsequent ENTER. 2. The key CANCEL may be used to cancel a nonconfirmed operation 1, 2, 3, 5 or 6, or a nofinished operation 3. 3. The operation 3 requires entering 4 numbers (each one finished by ENTER): x size in millimeters, y size in millimeters, resolution in dots per inch and mode (0=lineart, 1=grayscale, 2=color). 4. The operation 7 is used for training purposes. 5. When burning a CD in fast mode, scanning is forbidden. When burning a CD in slow mode, scanning is permitted. 6. The operation 8 reports if there exists enough compressed data to burn a full (640M) CD (sound 1 means "yes", sound 4 means "no", see the section "Sounds"). Sounds ------ As the scanning station operates without monitor, the operator knowns about the system status through 4 special sounds ("OK", "BUSY", "FAIL", "WARN") produced by a very crude interface to the PC speaker. To hear the sound 1, try the following command on the console: $ echo -e "\33[10;400]\33[11;200]\a" ; usleep 200000 ; \ echo -e "\33[10;650]\33[11;200]\a" ; usleep 200000 ; \ echo -e "\33[10;900]\33[11;150]\a" ; usleep 150000 To hear the other sounds, just take a look on how they're defined at make_beep() and build the corresponding shell command. 1. The sound 1 is used to inform the start or successfull end of a CD burn operation, and the successfull configuration of the scanner. 2. The sound 3 is used to inform the failure of a CD burning or scanner configuration operation. 3. The sound 2 is used to report CD-R busy on operation 4. The operation 4 reports the status of the last burn operation when the CD-R is not busy. 4. The sound 4 is a simple beep, used to inform about an invalid keypress, just like command-line interfaces do. 5. After scanning a page, the sound 1 will be played if the compressed pages already achieved 640M, so the station is ready to burn a CD. 6. If the station plays the sound 3 when the operator tries to scan a page, the disk is full, and scanning is forbidden until a CD burn operation is performed. 7. If the sound 3 is played for any operation, the manager detected a fatal error condition. In this case, try to exit the manager using operation 6. The init program will restart it and the sound 1 will be played. Try now to use the station. If the problem persists, try to shutdown the station using the operation (5). Switch the computer on again. If the problem persists, the manager is unable to handle the fatal error condition. Attach a console and examine the file /var/manager/log. Testing the manager ------------------- Before trying to test the manager, please read the following notes: 1. For tests, do not use a CD-R drive, but a CD-RW one. Define the value of "testing" as "1". The manager will blank the media before each burn test. Define the speeds FAST and SLOW accordingly to the drive and to the media. Remember that the system must be fast enough to support simultaneous scanning and slow CD-burning. Please make sure that SLOW and FAST are different speeds. 2. If a serial keyboard is not avaliable for tests, define the value of "have_kbd" as "0" and use a standard PC keyboard. In this case, use the numeric keypad keys 0-9, dot (as CANCEL) and ENTER. ENTER must be pressed after each key, except ENTER. 3. The scanning station manager uses Eric Youngdale's mkisofs and Joerg Schilling's cdrecord 1.9 to burn CD-Rs. The mkisofs version bundled with cdrecord-1.9 is used too, because we have had problems for producing multisession images using mkisofs 1.12b4 (we have not tried newer versions). SANE, gzip and GNU diff are also required. All binaries are referred through absolute paths because the manager requires root privileges (so maybe required to customize these paths, see the section PATHS on the source code). As we didn't found comments about the return code on the manpages of scanimage, mkisofs and cdrecord, the checkings for success or failure for these programs are somewhat indirect. 4. The following directories must be created by hand: /var/manager /var/manager/new 5. Change the device hardcoded definitions (sane_dev, cdrecord_dev, kbd_dev) accordingly to your system. Use the present definitions as examples, and see the SANE or cdrecord documentation if required. Change the serial parameters and adapt the keymap accordingly to your keyboard. 6. Define the value of "have_oper" as being 0 to generate automatically the operator actions "scan a page" or "burn a CD". Maintain one same document on the scanner and one CD-RW media loaded. In this case, the station will scan and burn CDs unattended until you kill the manager. It's a good test. 7. Define the value of "have_scanner" to 0 to simulate scanning by sleeping 50 seconds and copying the file /var/manager/foopage to /var/manager/newpage. Use this method if you do not have a scanner, or if you want to preserve the scanner lamp, or if you're debugging code unrelated to scanning. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* PATHS */ #define MAXPATH 128 char *LOGP = "/var/manager/log", *LOGP1 = "/var/manager/log.1", *LOGP2 = "/var/manager/log.2", *STDOUT = "/var/manager/cmd.stdout", *STDERR = "/var/manager/cmd.stderr", *CONFIG = "/var/manager/config", *NEWCONFIG = "/var/manager/config.new", *NEWPAGE = "/var/manager/newpage", *FOOPAGE = "/var/manager/foopage", *NEW = "/var/manager/new", *TOBURN = "/var/manager/toburn", *TOBURN1 = "/var/manager/toburn/1", *TOBURN2 = "/var/manager/toburn/2", *BURNED = "/var/manager/burned", *IMAGE1 = "/mnt/data/1.raw", *IMAGE2 = "/mnt/data/2.raw", *MV = "/bin/mv", *DIFF = "/usr/bin/diff", *RM = "/bin/rm", *CP = "/bin/cp", *CDRECORD = "/usr/local/bin/cdrecord", *MKISOFS = "/usr/local/src/mkisofs", *SCANIMAGE = "/usr/local/bin/scanimage", *DU = "/usr/bin/du", *GZIP = "/usr/bin/gzip", *MNTCD = "/mnt/cdrom"; /* Devices. */ char *kbd_dev = "/dev/ttyS3", *cdrecord_dev = "dev=1,0,0", *sane_dev = "umax:/dev/sgb" /* "microtek2:/dev/sgb" */; /* External processes. */ int scan_pid,burn_pid,compress_pid; /* Scanner configuration. */ int mode; int x0,y0,xsz,ysz,res; /* Current file ID. */ int fid; /* Various flags and statuses. */ int toburn_full=0, burn_st=0, burn_rc, burn_speed, burn_fail, fatal_failure=0; /* Test flags. */ int testing = 0, have_kbd = 1, have_oper = 1, have_scanner = 1, silently = 0; /* CD-R speed. */ int FAST = 4, SLOW = 2; /* Sounds, current sound and sound state. */ #define ALL_SND 0 #define OK_SND 1 #define BUSY_SND 2 #define FAIL_SND 3 #define WARN_SND 4 int sound = 0, sound_st, sound_rep, sound_all, term6 = -1; /* LOG descriptor. */ FILE *LOG=NULL; /* Log a message and optionally enter fatal failure state. */ void warn(int f,char *m, ...) { va_list args; char *ct,*a; time_t t; int s; struct stat fb; /* open log file */ if (LOG == NULL) { LOG = fopen(LOGP,"a"); } /* rotate logfile */ if ((LOG != NULL) && (stat(LOGP,&fb) == 0) && (fb.st_size > 1024*1024)) { fclose(LOG); rename(LOGP1,LOGP2); rename(LOGP,LOGP1); LOG = fopen(LOGP,"w"); } /* timestamp */ t = time(NULL); ct = ctime(&t); s = strlen(ct); a = alloca(strlen(ct)); strncpy(a,ct,s-1); a[s-1] = 0; /* log message */ if (LOG == NULL) { va_start(args,m); fprintf(stderr,"%s ",a); vfprintf(stderr,m,args); fprintf(stderr,"\n"); } else { va_start(args,m); fprintf(LOG,"%s ",a); vfprintf(LOG,m,args); fprintf(LOG,"\n"); fflush(LOG); } /* enter fatal failure state */ if (f) fatal_failure = 1; } /* Some disk procedures used more than once. */ void hd(int op) { char path[MAXPATH+1]; int e1,e2; struct stat fb; /* Move any files in TOBURN1 or TOBURN2 to TOBURN and unlinks TOBURN1 and TOBURN2. */ if (op == 1) { /* stat both dirs */ e1 = (stat(TOBURN1,&fb) == 0); e2 = (stat(TOBURN2,&fb) == 0); /* move */ if (e1) { snprintf(path,MAXPATH,"%s %s/* %s",MV,TOBURN1,TOBURN); path[MAXPATH] = 0; system(path); } if (e2) { snprintf(path,MAXPATH,"%s %s/* %s",MV,TOBURN2,TOBURN); path[MAXPATH] = 0; system(path); } /* unlink dirs */ if (e1 && (rmdir(TOBURN1) < 0)) warn(0,"could not remove %s",TOBURN1); if (e2 && (rmdir(TOBURN2) < 0)) warn(0,"could not remove %s",TOBURN2); } /* remove BURNED */ else if (op == 2) { snprintf(path,MAXPATH,"%s -rf %s",RM,BURNED); path[MAXPATH] = 0; system(path); } /* remove IMAGE1 */ else if (op == 3) { e1 = (stat(IMAGE1,&fb) == 0); if (e1 && (unlink(IMAGE1) < 0)) { warn(1,"could not remove %s",IMAGE1); } } /* remove IMAGE2 */ else if (op == 4) { e2 = (stat(IMAGE2,&fb) == 0); if (e2 && (unlink(IMAGE2) < 0)) { warn(1,"could not remove %s",IMAGE2); } } /* create TOBURN */ else if (op == 5) { if ((stat(TOBURN,&fb) < 0) && (errno == ENOENT)) { if (mkdir(TOBURN,0775) < 0) warn(1,"could not create %s",TOBURN); } } } /* Beep on frequency f by t milliseconds. Returns t*1000. */ int mybeep(int f,int t) { unsigned char msg[49]; if (!silently) { snprintf(msg,48,"%c[10;%d]%c[11;%d]\a",27,f,27,t); msg[48] = 0; write(term6,msg,strlen(msg)); } return(t*1000); } /* Produce some nice beeps. */ int make_beep(void) { int nt=0; if (sound == 0) return(0); /* sound "OK" */ if (sound == 1) { if (sound_st == 1) { nt = mybeep(400,200); ++sound_st; } else if (sound_st == 2) { nt = mybeep(650,200); ++sound_st; } else if (sound_st == 3) { nt = mybeep(900,150); sound_st = 0; } } /* sound "BUSY" */ else if (sound == 2) { if (sound_st == 1) { nt = 2 * mybeep(450,300); ++sound_st; } else { sound_st = 0; nt = 0; } } /* sound "FAILURE" */ else if (sound == 3) { if (sound_st <= 2) { if (sound_st & 1) { nt = mybeep(900,500); } else { nt = mybeep(600,800) + 300000; } ++sound_st; } else { sound_st = 0; nt = 0; } } /* normal warn "BEEP" */ else if (sound == 4) { if (sound_st == 1) { nt = mybeep(1000,200); sound_st = 0; } } /* sound "EMERGENCY" (bad results) */ else if (sound == 5) { if (sound_st < 80) { nt = mybeep((2-1/(((float)sound_st))/2+0.5)*400,15); nt -= 5000; ++sound_st; } else { sound_st = 0; } } return(nt); } /* Initialize sound_rep. */ void init_sound_rep(void) { if (sound == 1) sound_rep = 1; else if (sound == 2) sound_rep = 4; else if (sound == 3) sound_rep = 3; else if (sound == 4) sound_rep = 1; else sound_rep = 1; } /* SIGALRM handler. */ void handle_alrm(int p) { struct itimerval it; int nt; /* beep accordingly to current sound and step */ nt = make_beep(); /* prepare next repetition (if any) */ if ((sound_st <= 0) && (--sound_rep > 0)) { sound_st = 1; nt += 200000; } /* if sound_all is set, prepare next sound to play */ if ((sound_st <= 0) && (sound_all) && (sound < 4)) { ++sound; sound_st = 1; init_sound_rep(); nt += 500000; } /* set next alarm accordingly to make_beep answer */ if (sound_st > 0) { it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; it.it_value.tv_sec = nt / 1000000; it.it_value.tv_usec = nt % 1000000; setitimer(ITIMER_REAL,&it,NULL); } } /* Produce the specified sound. */ void make_sound(int id) { /* play all sounds */ if (id == ALL_SND) { sound = 1; sound_all = 1; } /* play only the specified sound */ else { sound = id; sound_all = 0; } /* prepare and start the handler */ sound_st = 1; init_sound_rep(); handle_alrm(0); } /* Free manager resources before exec. */ void prepare_child(void) { /* close descriptors */ if (LOG != NULL) fclose(LOG); if (term6 >= 0) close(term6); } /* Fork and exec. This service is to be used by burn_cd only because it always use the same fixed paths for capturing stdout and stderr. */ int fork_exec(const char *path, char *const argv[]) { int pid,F; /* fork failure */ if ((pid = fork()) < 0) { warn(1,"fork failure"); } /* this is the child */ else if (pid == 0) { /* free resources */ prepare_child(); /* capture stdout to STDOUT */ close(1); F = open(STDOUT,O_WRONLY|O_CREAT|O_TRUNC,0660); if ((F < 0) || ((F != 1) && (dup2(F,1) < 0))) { /* return nonzero status to the manager */ exit(255); } if ((F != 1) && (close(F) < 0)) warn(0,"error closing auxiliar descriptor"); /* capture stderr to STDERR */ close(2); F = open(STDERR,O_WRONLY|O_CREAT|O_TRUNC,0660); if ((F < 0) || ((F != 2) && (dup2(F,2) < 0))) { /* return nonzero status to the manager */ exit(255); } if ((F != 2) && (close(F) < 0)) warn(0,"error closing auxiliar descriptor"); /* exec */ execv(path,argv); /* return nonzero status to the manager */ exit(255); } /* this is the manager */ return(pid); } /* Search error message on command output. */ int search_error(char *msg) { FILE *F; char l[4097]; int i,s,t; /* message lenght */ s = strlen(msg); /* search on stdout */ if ((F = fopen(STDOUT,"r")) != NULL) { while (fgets(l,4096,F) != NULL) { t = strlen(l) - s; for (i=0; i= 255) warn(1,"line too long in config file"); /* read next parameter */ if (strncmp(s,"x0=",3) == 0) x0 = atoi(s+3); else if (strncmp(s,"y0=",3) == 0) y0 = atoi(s+3); else if (strncmp(s,"xsz=",4) == 0) xsz = atoi(s+4); else if (strncmp(s,"ysz=",4) == 0) ysz = atoi(s+4); else if (strncmp(s,"res=",4) == 0) res = atoi(s+4); else if (strncmp(s,"fid=",4) == 0) fid = atoi(s+4); else if (strncmp(s,"mode=",5) == 0) mode = atoi(s+5); else if (s[0] != '\n') warn(1,"invalid field in config file"); } /* finished reading */ fclose(F); /* test against some reasonable limits */ if (consist_sp(x0,y0,xsz,ysz,res,fid,mode) == 0) { warn(1,"invalid field in config file"); } } /* Configure scanner. */ void scan_conf(int c_xsz,int c_ysz,int c_res,int c_mode) { /* configure */ if (consist_sp(x0,y0,c_xsz,c_ysz,c_res,fid,c_mode) == 1) { xsz = c_xsz; ysz = c_ysz; res = c_res; mode = c_mode; write_conf(); make_sound(OK_SND); } /* beep */ else { make_sound(FAIL_SND); } } /* Obtain exit status for some process. */ void my_wait(void) { int s,p; char to[MAXPATH+1]; /* nothing to wait */ if ((scan_pid < 0) && (burn_pid < 0) && (compress_pid < 0)) return; /* someone exited? */ p = waitpid(-1,&s,WNOHANG); /* nobody exited */ if (p == 0) return; /* wait error */ else if (p < 0) { warn(0,"wait error caught"); } /* yes, someone exited */ else { /* finished scanning */ if (p == scan_pid) { struct stat fb; int size; warn(0,"scanning finished"); if (toburn_full) make_sound(OK_SND); /* expected minimum file size */ size = xsz * (res/25.4) * ysz * (res/25.4); if (mode == 0) size /= 8; else if (mode == 2) size *= 3; /* cannot stat, assume that scanimage failed and continue */ if (stat(NEWPAGE,&fb) < 0) { warn(0,"scanimage failed"); } /* explicit scanning failure or file too small */ if ((WIFEXITED(s) == 0) || (fb.st_size < size)) { warn(0,"scanimage failed"); /* unlink */ if (unlink(NEWPAGE) < 0) warn(1,"could not unlink %s",NEWPAGE); } /* move the just acquired page to NEW */ else { /* save new file id */ ++fid; write_conf(); /* move */ snprintf(to,MAXPATH-4,"%s/%d",NEW,fid); to[MAXPATH-4] = 0; if (mode == 0) strcat(to,".pbm"); else if (mode == 1) strcat(to,".pgm"); else strcat(to,".pnm"); if (rename(NEWPAGE,to) < 0) warn(1,"failed to move %s",NEWPAGE); } /* no scanning process anymore */ scan_pid = -1; } /* finished some burning step */ else if (p == burn_pid) { burn_pid = -1; if (WIFEXITED(s)) { burn_rc = WEXITSTATUS(s); } } /* finished compressing */ else if (p == compress_pid) { warn(0,"compress finished"); compress_pid = -1; } } } /* Scan a page. */ int scan_page(void) { int F; char xsz_str[21],ysz_str[21],res_str[21],*mode_str; int pid; /* fork failure */ if ((pid = fork()) < 0) { warn(1,"fork failure"); } /* this is the child */ else if (pid == 0) { /* free resources */ prepare_child(); /* open file to dump stdout */ close(1); F = open(NEWPAGE,O_WRONLY|O_CREAT|O_TRUNC,0600); if (F < 0) warn(1,"could not open %s",NEWPAGE); /* redirect stdout */ if (F != 1) { if (dup2(F,1) < 0) { /* return nonzero status to the manager */ exit(255); } } /* prepare parameters */ snprintf(xsz_str,20,"%d",xsz); snprintf(ysz_str,20,"%d",ysz); snprintf(res_str,20,"%d",res); xsz_str[20] = 0; ysz_str[20] = 0; res_str[20] = 0; if (mode == 0) mode_str = "lineart"; else if (mode == 1) mode_str = "gray"; else mode_str = "color"; /* scan */ if (have_scanner) { execl(SCANIMAGE,"scanimage", "-d",sane_dev, "--mode",mode_str, "-x",xsz_str, "-y",ysz_str, "--resolution",res_str, NULL); } else { sleep(50); execl(CP,"cp",FOOPAGE,NEWPAGE,NULL); } /* this should not happen */ exit(255); } /* this is the manager */ else scan_pid = pid; /* successfully started the scan process */ return(1); } /* Try to move one page to the directory TOBURN. */ void move_toburn(void) { DIR *D; FILE *F; int t,du; struct dirent *e; char *a,f[MAXPATH+1],h[MAXPATH+1],r[256],path[MAXPATH+1]; struct stat fb; /* While compressing, we forbid moving a .gz file because maybe gzip is creating it just now. */ if (compress_pid >= 0) return; /* Get first filename (if any). This strategy is OK if all files have similar size. */ if ((D = opendir(NEW)) == NULL) { warn(1,"could not open directory %s",NEW); } for (f[0]=0; (f[0]==0) && ((e=readdir(D)) != NULL); ) { /* if the spec match, select it */ a = e->d_name; t = strlen(a); if ((t >= 8) && ((strcmp(a+t-7,".pbm.gz")==0) || (strcmp(a+t-7,".pgm.gz")==0) || (strcmp(a+t-7,".pnm.gz")==0))) { strcpy(f,NEW); strcat(f,"/"); strcpy(h,TOBURN); strcat(h,"/"); if (((strlen(f)+t) <= MAXPATH) && ((strlen(h)+t) <= MAXPATH)) { strcat(f,a); strcat(h,a); } else f[0] = 0; } } closedir(D); /* no file to move */ if (f[0] == 0) return; /* create TOBURN */ hd(5); /* get disk usage at TOBURN */ snprintf(path,MAXPATH,"%s -s -b %s",DU,TOBURN); path[MAXPATH] = 0; F = popen(path,"r"); fgets(r,255,F); pclose(F); du = atoi(r); /* add file size */ if (stat(f,&fb) < 0) warn(1,"cannot stat file"); du += fb.st_size; /* too much */ if (du > (625*1024*1024)) { toburn_full = 1; warn(0,"toburn is full"); make_sound(OK_SND); return; } warn(0,"moving %s to %s",f,h); /* move */ if (rename(f,h) < 0) { warn(1,"could not move to %s",TOBURN); } } /* Try to compress a page. */ void start_compress(void) { DIR *D; int t,pid; struct dirent *e; char *a,f[MAXPATH+1]; /* already compressing */ if (compress_pid >= 0) return; /* get first filename (if any) */ if ((D = opendir(NEW)) == NULL) { warn(1,"could not open directory %s",NEW); } for (f[0]=0; (f[0]==0) && ((e=readdir(D)) != NULL); ) { /* if the spec match, select it */ a = e->d_name; t = strlen(a); if ((t >= 5) && ((strcmp(a+t-4,".pbm")==0) || (strcmp(a+t-4,".pgm")==0) || (strcmp(a+t-4,".pnm")==0))) { snprintf(f,MAXPATH,"%s/",NEW); f[MAXPATH] = 0; if ((strlen(f)+t) <= MAXPATH) strcat(f,a); else f[0] = 0; } } closedir(D); /* no file to compress */ if (f[0] == 0) return; /* fork failure */ if ((pid = fork()) < 0) { warn(1,"fork failure"); } /* this is the child */ else if (pid == 0) { /* free resources */ prepare_child(); warn(0,"going to compress %s",f); /* compress */ execl(GZIP,"gzip","-9",f,NULL); /* this should not happen */ exit(255); } /* this is the manager */ else compress_pid = pid; } /* Burn a CD. The images are created on the IDE disk mounted at /mnt/data because the SCSI bus is busy with the UMAX scanner. As the IDE disk has only 540M, we burn a multisession CD. */ void burn_cd(void) { char path[MAXPATH+1]; char *argv[20],speed[17]; static int sz1,sz2,mnt; struct stat fb; /* must wait */ if (burn_pid >= 0) return; /* sentinel */ path[MAXPATH] = 0; /* burning speed */ snprintf(speed,16,"speed=%d",burn_speed); speed[16] = 0; /* STEP 1 ------ blank media (for tests using CD-RW) */ if (burn_st == 1) { make_sound(OK_SND); warn(0,"blanking the media"); if (testing) { strncpy(path,CDRECORD,MAXPATH); argv[0] = "cdrecord"; argv[1] = "-blank=fast"; argv[2] = speed; argv[3] = cdrecord_dev; argv[4] = NULL; burn_pid = fork_exec(path,argv); } ++burn_st; } /* STEP 2 ------ divide toburn in two parts */ else if (burn_st == 2) { DIR *D; int t,*sz; struct dirent *e; char *a,f[MAXPATH+1],h[MAXPATH+1]; /* reset statuses */ mnt = 0; burn_fail = 0; /* create subdirectories "1" and "2" */ strncpy(f,TOBURN1,MAXPATH); f[MAXPATH] = 0; if ((mkdir(f,0755) < 0) && (errno != EEXIST)) warn(1,"could not create %s",f); strncpy(f,TOBURN2,MAXPATH); f[MAXPATH] = 0; if ((mkdir(f,0755) < 0) && (errno != EEXIST)) warn(1,"could not create %s",f); /* divide loop */ if ((D = opendir(TOBURN)) == NULL) { warn(1,"could not open directory %s",TOBURN); } for (sz1=sz2=0; (e=readdir(D)) != NULL; ) { /* if the filename spec matches */ a = e->d_name; t = strlen(a); if ((t >= 8) && ((strcmp(a+t-7,".pbm.gz")==0) || (strcmp(a+t-7,".pgm.gz")==0) || (strcmp(a+t-7,".pnm.gz")==0))) { /* choose directory 1 or 2 */ if (sz1 <= sz2) { snprintf(h,MAXPATH,"%s/",TOBURN1); h[MAXPATH] = 0; sz = &sz1; } else { snprintf(h,MAXPATH,"%s/",TOBURN2); h[MAXPATH] = 0; sz = &sz2; } /* build full paths */ snprintf(f,MAXPATH,"%s/",TOBURN); f[MAXPATH] = 0; if (((strlen(f)+t) <= MAXPATH) && ((strlen(h)+t) <= MAXPATH)) { strcat(f,a); strcat(h,a); } else { warn(1,"strange filename"); } /* add file size */ if (stat(f,&fb) < 0) warn(1,"cannot stat file"); *sz += fb.st_size; /* move */ if (rename(f,h) < 0) { warn(1,"could not move file while dividing"); } } } closedir(D); /* nothing to burn */ if ((sz1 <= 0) && (sz2 <= 0)) { warn(0,"nothing to burn"); burn_fail = 1; } ++burn_st; } /* STEP 3 ------ masterize the first part */ else if (burn_st == 3) { if (sz1 > 0) { warn(0,"ready to create the first image"); strncpy(path,MKISOFS,MAXPATH); argv[0] = "mkisofs"; argv[1] = "-r"; argv[2] = "-J"; argv[3] = "-o"; argv[4] = IMAGE1; argv[5] = TOBURN1; argv[6] = NULL; burn_pid = fork_exec(path,argv); } ++burn_st; } /* STEP 4 ------ burn the first part */ else if (burn_st == 4) { int ie; /* image exists and has reasonable size? */ if ((sz1 > 0) && (((ie=stat(IMAGE1,&fb)) < 0) || (fb.st_size < sz1))) { if (ie == 0) warn(0,"image 1.raw not ok, waiting >%d found %d",sz1,fb.st_size); else warn(0,"didn't find image 1.raw"); burn_fail = 1; } /* burn it */ if ((!burn_fail) && (sz1 > 0)) { warn(0,"going to burn the first image"); strncpy(path,CDRECORD,MAXPATH); argv[0] = "cdrecord"; argv[1] = "-multi"; argv[2] = speed; argv[3] = cdrecord_dev; argv[4] = IMAGE1; argv[5] = NULL; burn_pid = fork_exec(path,argv); } ++burn_st; } /* STEP 5 ------ masterize the second part */ else if (burn_st == 5) { FILE *F; char r[256]; int t,retries; int c1,c2,c3; /* remove the first image */ hd(3); /* search error message */ if (search_error("I/O error") || search_error("Input/output error") || search_error("Medium Error")) { burn_fail = 1; } if ((!burn_fail) && (sz2 > 0)) { /* Get the session start numbers. As the drive sometimes is not ready at this point, we try three times. */ for (retries=3, r[0]=0; (r[0]==0) && (retries>0); --retries) { warn(0,"will run cdrecord -msinfo"); /* run cdrecord -msinfo */ snprintf(path,MAXPATH,"%s -msinfo %s",CDRECORD,cdrecord_dev); path[MAXPATH] = 0; F = popen(path,"r"); fgets(r,255,F); pclose(F); r[255] = 0; /* remove LF */ t = strlen(r) - 1; while ((t>=0) && ((r[t]== '\r') || (r[t]== '\n'))) r[t--] = 0; warn(0,"cdrecord answer is %s",r); /* count digits and commas */ for (c3=0; ((t>=0) && (('0'<=r[t]) && (r[t]<='9'))); ++c3, --t); for (c2=0; ((t>=0) && (r[t]==',')); ++c2, --t); for (c1=0; ((t>=0) && (('0'<=r[t]) && (r[t]<='9'))); ++c1, --t); /* failure */ if ((t >= 0) || (c1 <= 0) || (c2 <= 0) || (c3 <= 0)) { warn(0,"failure: t=%d c1=%d c2=%d c3=%d",t,c1,c2,c3); sleep(3); r[0] = 0; } } /* failed, ignore the second part */ if (r[0] == 0) burn_fail = 1; /* create the second image */ if ((!burn_fail) && (sz2 > 0)) { char *cddev; /* skip the dev= or -dev= portion of cdrecord_dev */ for (cddev=cdrecord_dev; (*cddev!='=') && (*cddev!=0); ++cddev); if (*cddev == '=') ++cddev; else warn(1,"invalid cdrecord device %s",cdrecord_dev); warn(0,"ready to create the second image"); strncpy(path,MKISOFS,MAXPATH); argv[0] = "mkisofs"; argv[1] = "-r"; argv[2] = "-J"; argv[3] = "-C"; argv[4] = r; argv[5] = "-M"; argv[6] = cddev; argv[7] = "-o"; argv[8] = IMAGE2; argv[9] = TOBURN2; argv[10] = NULL; burn_pid = fork_exec(path,argv); } } ++burn_st; } /* STEP 6 ------ burn the second part */ else if (burn_st == 6) { int ie; /* image exists and has reasonable size? */ if ((sz2 > 0) && (((ie=stat(IMAGE2,&fb)) < 0) || (fb.st_size < sz2))) { if (ie == 0) warn(0,"image 2.raw not ok, waiting >%d found %d",sz2,fb.st_size); else warn(0,"didn't find image 2.raw"); burn_fail = 1; } /* burn it */ if ((!burn_fail) && (sz2 > 0)) { warn(0,"going to burn the second image"); strncpy(path,CDRECORD,MAXPATH); argv[0] = "cdrecord"; argv[1] = speed; argv[2] = cdrecord_dev; argv[3] = "-data"; argv[4] = "-pad"; argv[5] = IMAGE2; argv[6] = NULL; burn_pid = fork_exec(path,argv); } ++burn_st; } /* STEP 7 ------ compare */ else if (burn_st == 7) { /* remove the second image */ hd(4); /* search error message */ if (search_error("I/O error") || search_error("Input/output error") || search_error("Medium Error")) { burn_fail = 1; } /* move back and unlink dirs */ hd(1); /* mount the media run comparing procedure */ if ((!burn_fail) && ((sz1 > 0) || (sz2 > 0))) { warn(0,"now will mount the media and compare"); /* mount CD */ system("/bin/mount -t iso9660 /dev/scd0 /mnt/cdrom"); mnt = 1; /* compare */ burn_rc = -1; strncpy(path,DIFF,MAXPATH); path[MAXPATH] = 0; argv[0] = "diff"; argv[1] = TOBURN; argv[2] = MNTCD; argv[3] = NULL; burn_pid = fork_exec(path,argv); } ++burn_st; } /* STEP 8 ------ clear TOBURN and reset */ else if (burn_st == 8) { /* umount CD */ if (mnt) { warn(0,"umounting the media"); system("/bin/umount /mnt/cdrom"); mnt = 0; } if ((sz1 > 0) || (sz2 > 0)) { /* uh! */ if ((burn_fail) || (burn_rc != 0)) { if (burn_fail) { warn(0,"CD is not OK (early failure)"); } else { warn(0,"CD is not OK (code %d)",burn_rc); burn_fail = 1; } } /* remove pages */ else { warn(0,"CD is OK (code %d)",burn_rc); warn(0,"removing scanned pages"); if (rename(TOBURN,BURNED) < 0) { warn(1,"could not rename %s to %s",TOBURN,BURNED); } hd(2); toburn_full = 0; } } /* reset and beep */ burn_st = 0; if (burn_fail == 0) { warn(0,"finished burning procedure (success)"); make_sound(OK_SND); } else { warn(0,"finished burning procedure (failure, status is %d)",burn_fail); make_sound(FAIL_SND); } } } /* Generate key */ int genkey(char *kbuf) { int r=0; char lf; static int scan_at=-1,burn_at=-1,next=-1; time_t t; /* the operator decides using the keyboard */ if (have_oper) { r = read(0,kbuf,1); if ((r > 0) && (*kbuf != '\n')) read(0,&lf,1); if (r > 0) { switch (*kbuf) { case '1': *kbuf = 'b' ; break; case '2': *kbuf = 'R' ; break; case '3': *kbuf = 'J' ; break; case '4': *kbuf = 'a' ; break; case '5': *kbuf = 'Q' ; break; case '6': *kbuf = 'I' ; break; case '7': *kbuf = 'd' ; break; case '8': *kbuf = 'T' ; break; case '9': *kbuf = 'L' ; break; case '.': *kbuf = '`' ; break; case '0': *kbuf = 'P' ; break; case '\n': *kbuf = 'H' ; break; default: *kbuf = 0; } } } /* generate automatic actions (for tests) */ else { /* bufferized key */ if (next >= 0) { *kbuf = next; r = 1; next = -1; } else { t = time(NULL); /* schedule or generate scan command */ if (scan_pid < 0) { if (scan_at < 0) { scan_at = t + 5 + (rand() % 10); } else if (t >= scan_at) { r = 1; *kbuf = 'H'; scan_at = -1; } } /* schedule or generate burn CD command */ if ((r==0) && (toburn_full) && (burn_st <= 0)) { if (burn_at < 0) { burn_at = t + 10 + (rand() % 20); } else if (t >= burn_at) { r = 1; *kbuf = 'b'; next = 'H'; burn_at = -1; } } } } return(r); } /* The program begins here. */ int main(int argc,char *argv[]) { /* key and number input buffers */ char kbuf,nbuf[49]; int m,n,ni,np; /* serial keyboard map */ char map[256]; /* serial I/O */ struct termios oldtio,tio; int fd,r; /* user interface: operation and state in course */ int op,st; /* candidate parameters for configuring the scanner */ int c_xsz,c_ysz,c_res,c_mode; /* counters and flags */ int i,f,l; /* random seed */ srand(0); /* open keyboard */ if (have_kbd) { fd = open(kbd_dev,O_RDONLY|O_NOCTTY|O_NONBLOCK); if (fd <0) { warn(1,"coud not find keyboard"); } } else { fd = 0; fcntl(0,F_SETFL,O_NONBLOCK); } /* open terminal 6 (for producing beeps) */ term6 = open("/dev/tty6",O_WRONLY); /* install handler */ signal(SIGALRM,handle_alrm); /* various defaults */ scan_pid = -1; burn_pid = -1; compress_pid = -1; /* read configuration */ read_conf(); /* save current port settings */ if (have_kbd) tcgetattr(fd,&oldtio); /* Create the keyboard mapping accordingly to the following table: +----------------------+ | | CODE | | KEY |----------------| | |ASC DEC BIN | +----------------------+ | 1 | b 98 1100010| | 2 | R 82 1010010| | 3 | J 74 1001010| | 4 | a 97 1100001| | 5 | Q 81 1010001| | 6 | I 73 1001001| | 7 | d 100 1100100| | 8 | T 84 1010100| | 9 | L 76 1001100| |ANULA| ` 96 1100000| | 0 | P 80 1010000| |ENTRA| H 72 1001000| +----------------------+ */ for (i=0; i<256; ++i) map[i] = 0; map['b'] = '1'; map['R'] = '2'; map['J'] = '3'; map['a'] = '4'; map['Q'] = '5'; map['I'] = '6'; map['d'] = '7'; map['T'] = '8'; map['L'] = '9'; map['`'] = 'A'; map['P'] = '0'; map['H'] = 'E'; if (have_kbd) { /* configure */ bzero(&tio,sizeof(tio)); tio.c_cflag = B1200 | CS7 | CLOCAL | CREAD; tio.c_iflag = IGNPAR; tio.c_oflag = 0; tio.c_lflag = 0; tio.c_cc[VTIME] = 0; /* number of characters to read */ tio.c_cc[VMIN] = 1; /* prepare */ tcflush(fd,TCIFLUSH); tcsetattr(fd,TCSANOW,&tio); } /* just to avoid a compilation warning */ np = 0; st = 0; c_xsz = 0; c_ysz = 0; c_res = 0; /* tell the operator that we're alive */ make_sound(OK_SND); /* prepare directories */ hd(1); hd(2); hd(3); hd(4); /* Main loop for reading the keyboard and activating the various handlers. */ for (f=ni=op=l=0; f==0; ) { /* try to read one keypress */ if (have_kbd) r = read(fd,&kbuf,1); else r = genkey(&kbuf); /* fatal failure: just beep */ if ((r > 0) && (fatal_failure)) { m = map[(int)kbuf]; if (((ni == 0) && (m == 5)) || ((ni == 0) && (m == 5)) || ((ni == 2) && (op == 2)) || ((ni == 2) && (op == 5))) { /* down or exit in course: process key */ } /* ignore anything else */ else { make_sound(FAIL_SND); r = 0; } } /* got one! */ if (r > 0) { /* print the translated character (for tests) */ warn(0,"read %c from keyboard",map[*((unsigned char *)&kbuf)]); /* map */ m = map[(int)kbuf]; /* cancel ongoing input (if any) */ if (m == 'A') { ni = 0; make_sound(OK_SND); } /* This block processes ongoing operations waiting a simple (ENTER) confirmation */ else if (ni == 2) { /* not confirmed: reset */ if (m != 'E') { make_sound(WARN_SND); } /* shutdown confirmed */ else if (op == 2) { warn(0,"going down"); make_sound(OK_SND); /* execl("/sbin/shutdown","-h","now",NULL); */ } /* burn CD (slow) confirmed */ else if (op == 3) { burn_speed = SLOW; burn_st = 1; } /* burn CD (fast) confirmed */ else if (op == 4) { burn_speed = FAST; burn_st = 1; } /* exit confirmed */ else if (op == 5) { f = 1; } ni = 0; } /* This block processes operations waiting numeric input */ else if (ni == 1) { /* Numeric input finished. Convert and discover what to do with the number. */ if (m == 'E') { nbuf[np] = 0; n = atoi(nbuf); /* use it as a parameter for scanner configuration */ if (op == 1) { /* use if as xsz and continue reading */ if (st == 1) { c_xsz = n; st = 2; } /* use if as ysz and continue reading */ else if (st == 2) { c_ysz = n; st = 3; } /* use if as resolution and continue reading */ else if (st == 3) { c_res = n; st = 4; } /* use if as mode and finish */ else if (st == 4) { c_mode = n; scan_conf(c_xsz,c_ysz,c_res,c_mode); ni = 0; } } } /* concatenate digit and continue reading */ else if (np <= 48) nbuf[np++] = m; } /* configure scanner */ else if (m == '3') { /* busy: ignore */ if ((scan_pid >= 0) || (burn_st >= 0) || (compress_pid >= 0)) { make_sound(BUSY_SND); } /* enter configuration state */ else { ni = 1; np = 0; op = 1; st = 1; } } /* start scanning */ else if (m == 'E') { struct statfs fbfs; /* forbid if already scanning or burning CD in fast mode */ if ((scan_pid < 0) && ((burn_speed==SLOW) || (burn_st==0))) { /* forbid if <100M free */ if (statfs("/",&fbfs) < 0) warn(1,"cannot statfs root"); if (fbfs.f_bavail > 100*1024*1024/fbfs.f_bsize) { scan_page(); } else { warn(0,"disk full"); if (have_oper) make_sound(FAIL_SND); } } else make_sound(WARN_SND); } /* start burning CD (slow) */ else if (m == '1') { if (burn_st == 0) { ni = 2; op = 3; } else make_sound(WARN_SND); } /* start burning CD (fast) */ else if (m == '2') { if ((burn_st == 0) && (scan_pid < 0)) { ni = 2; op = 4; } else make_sound(WARN_SND); } /* test CD-R status */ else if (m == '4') { if (burn_st > 0) make_sound(BUSY_SND); else if (burn_fail) make_sound(FAIL_SND); else make_sound(OK_SND); } /* shutdown */ else if (m == '5') { warn(0,"down requested"); ni = 2; op = 2; } /* exit (to be restarted by initd) */ else if (m == '6') { warn(0,"exiting"); ni = 2; op = 5; } /* play all sounds */ else if (m == '7') { make_sound(ALL_SND); } /* disk usage */ else if (m == '8') { if (toburn_full) make_sound(OK_SND); else make_sound(WARN_SND); } } /* sleep 0.1 seconds */ else { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100000; select(0,NULL,NULL,NULL,&tv); } /* each 0.5 seconds try to perform some op */ if (++l >= 5) { /* try to compress a file */ start_compress(); /* try to perform one burn step */ if ((burn_st > 0) && (burn_pid < 0)) burn_cd(); /* wait children */ my_wait(); /* try to move a file */ if ((!toburn_full) && (compress_pid < 0)) move_toburn(); /* reset counter */ l = 0; } } /* restore and exit */ if (have_kbd) tcsetattr(fd,TCSANOW,&oldtio); if (LOG != NULL) fclose(LOG); if (term6 >= 0) close(term6); exit(0); }