1/* work.c 2 Routines to read command files. 3 4 Copyright (C) 1991, 1992, 1993, 1995, 2002 Ian Lance Taylor 5 6 This file is part of the Taylor UUCP package. 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License as 10 published by the Free Software Foundation; either version 2 of the 11 License, or (at your option) any later version. 12 13 This program is distributed in the hope that it will be useful, but 14 WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 21 22 The author of the program may be contacted at ian@airs.com. 23 */ 24 25#include "uucp.h" 26 27#if USE_RCS_ID 28const char work_rcsid[] = "$Id: work.c,v 1.24 2002/03/05 19:10:42 ian Rel $"; 29#endif 30 31#include "uudefs.h" 32#include "uuconf.h" 33#include "system.h" 34#include "sysdep.h" 35 36#include <ctype.h> 37#include <errno.h> 38 39#if HAVE_OPENDIR 40#if HAVE_DIRENT_H 41#include <dirent.h> 42#else /* ! HAVE_DIRENT_H */ 43#include <sys/dir.h> 44#define dirent direct 45#endif /* ! HAVE_DIRENT_H */ 46#endif /* HAVE_OPENDIR */ 47 48/* Local functions. */ 49 50static char *zswork_directory P((const char *zsystem)); 51static boolean fswork_file P((const char *zsystem, const char *zfile, 52 char *pbgrade)); 53static int iswork_cmp P((constpointer pkey, constpointer pdatum)); 54 55/* These functions can support multiple actions going on at once. 56 This allows the UUCP package to send and receive multiple files at 57 the same time. */ 58 59/* The ssfilename structure holds the name of a work file, as well as 60 its grade. */ 61 62struct ssfilename 63{ 64 char *zfile; 65 char bgrade; 66 /* Some compiler may need this, and it won't normally hurt. */ 67 char bdummy; 68}; 69 70/* The ssfile structure holds a command file name and all the lines 71 read in from that command file. The union within the ssline 72 structure initially holds a line from the file and then holds a 73 pointer back to the ssfile structure; a pointer to this union is 74 used as a sequence pointer. The ztemp entry of the ssline 75 structure holds the name of a temporary file to delete, if any. */ 76 77#define CFILELINES (10) 78 79struct ssline 80{ 81 char *zline; 82 struct ssfile *qfile; 83 char *ztemp; 84}; 85 86struct ssfile 87{ 88 char *zfile; 89 char bgrade; 90 /* bdummy is needed for some buggy compilers. */ 91 char bdummy; 92 int clines; 93 int cdid; 94 struct ssline aslines[CFILELINES]; 95}; 96 97/* Static variables for the work scan. */ 98 99static struct ssfilename *asSwork_files; 100static size_t cSwork_files; 101static size_t iSwork_file; 102static struct ssfile *qSwork_file; 103 104/* Given a system name, return a directory to search for work. */ 105 106static char * 107zswork_directory (zsystem) 108 const char *zsystem; 109{ 110#if SPOOLDIR_V2 111 return zbufcpy ("."); 112#endif /* SPOOLDIR_V2 */ 113#if SPOOLDIR_BSD42 || SPOOLDIR_BSD43 114 return zbufcpy ("C."); 115#endif /* SPOOLDIR_BSD42 || SPOOLDIR_BSD43 */ 116#if SPOOLDIR_HDB || SPOOLDIR_SVR4 117 return zbufcpy (zsystem); 118#endif /* SPOOLDIR_HDB || SPOOLDIR_SVR4 */ 119#if SPOOLDIR_ULTRIX 120 return zsappend3 ("sys", 121 (fsultrix_has_spool (zsystem) 122 ? zsystem 123 : "DEFAULT"), 124 "C."); 125#endif /* SPOOLDIR_ULTRIX */ 126#if SPOOLDIR_TAYLOR 127 return zsysdep_in_dir (zsystem, "C."); 128#endif /* SPOOLDIR_TAYLOR */ 129} 130 131/* See whether a file name from the directory returned by 132 zswork_directory is really a command for a particular system. 133 Return the command grade. */ 134 135/*ARGSUSED*/ 136static boolean 137fswork_file (zsystem, zfile, pbgrade) 138 const char *zsystem ATTRIBUTE_UNUSED; 139 const char *zfile; 140 char *pbgrade; 141{ 142#if SPOOLDIR_V2 || SPOOLDIR_BSD42 || SPOOLDIR_BSD43 || SPOOLDIR_ULTRIX 143 int cfilesys, csys; 144 145 /* The file name should be C.ssssssgqqqq, where g is exactly one 146 letter and qqqq is exactly four numbers. The system name may be 147 truncated to six or seven characters. The system name of the 148 file must match the system name we're looking for, since there 149 could be work files for several systems in one directory. */ 150 if (zfile[0] != 'C' || zfile[1] != '.') 151 return FALSE; 152 csys = strlen (zsystem); 153 cfilesys = strlen (zfile) - 7; 154 if (csys != cfilesys 155 && (csys < 6 || (cfilesys != 6 && cfilesys != 7))) 156 return FALSE; 157 *pbgrade = zfile[cfilesys + 2]; 158 return strncmp (zfile + 2, zsystem, cfilesys) == 0; 159#endif /* V2 || BSD42 || BSD43 || ULTRIX */ 160#if SPOOLDIR_HDB || SPOOLDIR_SVR4 161 int clen; 162 163 /* The HDB file name should be C.ssssssgqqqq where g is exactly one 164 letter and qqqq is exactly four numbers or letters. We don't 165 check the system name, because it is guaranteed by the directory 166 we are looking in and some versions of uucp set it to the local 167 system rather than the remote one. I'm not sure of the exact 168 format of the SVR4 file name, but it does not include the grade 169 at all. */ 170 if (zfile[0] != 'C' || zfile[1] != '.') 171 return FALSE; 172 clen = strlen (zfile); 173 if (clen < 7) 174 return FALSE; 175#if ! SPOOLDIR_SVR4 176 *pbgrade = zfile[clen - 5]; 177#endif 178 return TRUE; 179#endif /* SPOOLDIR_HDB || SPOOLDIR_SVR4 */ 180#if SPOOLDIR_TAYLOR 181 /* We don't keep the system name in the file name, since that 182 forces truncation. Our file names are always C.gqqqq. */ 183 *pbgrade = zfile[2]; 184 return (zfile[0] == 'C' 185 && zfile[1] == '.' 186 && zfile[2] != '\0'); 187#endif /* SPOOLDIR_TAYLOR */ 188} 189 190/* A comparison function to look through the list of file names. */ 191 192static int 193iswork_cmp (pkey, pdatum) 194 constpointer pkey; 195 constpointer pdatum; 196{ 197 const struct ssfilename *qkey = (const struct ssfilename *) pkey; 198 const struct ssfilename *qdatum = (const struct ssfilename *) pdatum; 199 200 return strcmp (qkey->zfile, qdatum->zfile); 201} 202 203/* See whether there is any work to do for a particular system. */ 204 205boolean 206fsysdep_has_work (qsys) 207 const struct uuconf_system *qsys; 208{ 209 char *zdir; 210 DIR *qdir; 211 struct dirent *qentry; 212#if SPOOLDIR_SVR4 213 DIR *qgdir; 214 struct dirent *qgentry; 215#endif 216 217 zdir = zswork_directory (qsys->uuconf_zname); 218 if (zdir == NULL) 219 return FALSE; 220 qdir = opendir ((char *) zdir); 221 if (qdir == NULL) 222 { 223 ubuffree (zdir); 224 return FALSE; 225 } 226 227#if SPOOLDIR_SVR4 228 qgdir = qdir; 229 while ((qgentry = readdir (qgdir)) != NULL) 230 { 231 char *zsub; 232 233 if (qgentry->d_name[0] == '.' 234 || qgentry->d_name[1] != '\0') 235 continue; 236 zsub = zsysdep_in_dir (zdir, qgentry->d_name); 237 qdir = opendir (zsub); 238 ubuffree (zsub); 239 if (qdir == NULL) 240 continue; 241#endif 242 243 while ((qentry = readdir (qdir)) != NULL) 244 { 245 char bgrade; 246 247 if (fswork_file (qsys->uuconf_zname, qentry->d_name, &bgrade)) 248 { 249 closedir (qdir); 250#if SPOOLDIR_SVR4 251 closedir (qgdir); 252#endif 253 ubuffree (zdir); 254 return TRUE; 255 } 256 } 257 258#if SPOOLDIR_SVR4 259 closedir (qdir); 260 } 261 qdir = qgdir; 262#endif 263 264 closedir (qdir); 265 ubuffree (zdir); 266 return FALSE; 267} 268 269/* Initialize the work scan. We have to read all the files in the 270 work directory, so that we can sort them by work grade. The bgrade 271 argument is the minimum grade to consider. We don't want to return 272 files that we have already considered; usysdep_get_work_free will 273 clear the data out when we are done with the system. This returns 274 FALSE on error. */ 275 276#define CWORKFILES (10) 277 278boolean 279fsysdep_get_work_init (qsys, bgrade, cmax) 280 const struct uuconf_system *qsys; 281 int bgrade; 282 unsigned int cmax; 283{ 284 char *zdir; 285 DIR *qdir; 286 struct dirent *qentry; 287 size_t chad; 288 size_t callocated; 289#if SPOOLDIR_SVR4 290 DIR *qgdir; 291 struct dirent *qgentry; 292#endif 293 294 zdir = zswork_directory (qsys->uuconf_zname); 295 if (zdir == NULL) 296 return FALSE; 297 298 qdir = opendir (zdir); 299 if (qdir == NULL) 300 { 301 boolean fret; 302 303 if (errno == ENOENT) 304 fret = TRUE; 305 else 306 { 307 ulog (LOG_ERROR, "opendir (%s): %s", zdir, strerror (errno)); 308 fret = FALSE; 309 } 310 ubuffree (zdir); 311 return fret; 312 } 313 314 chad = cSwork_files; 315 callocated = cSwork_files; 316 317 /* Sort the files we already know about so that we can check the new 318 ones with bsearch. It would be faster to use a hash table, and 319 the code should be probably be changed. The sort done at the end 320 of this function does not suffice because it only includes the 321 files added last time, and does not sort the entire array. Some 322 (bad) qsort implementations are very slow when given a sorted 323 array, which causes particularly bad effects here. */ 324 if (chad > 0) 325 qsort ((pointer) asSwork_files, chad, sizeof (struct ssfilename), 326 iswork_cmp); 327 328#if SPOOLDIR_SVR4 329 qgdir = qdir; 330 while ((qgentry = readdir (qgdir)) != NULL) 331 { 332 char *zsub; 333 334 if (qgentry->d_name[0] == '.' 335 || qgentry->d_name[1] != '\0' 336 || UUCONF_GRADE_CMP (bgrade, qgentry->d_name[0]) < 0) 337 continue; 338 zsub = zsysdep_in_dir (zdir, qgentry->d_name); 339 qdir = opendir (zsub); 340 if (qdir == NULL) 341 { 342 if (errno != ENOTDIR && errno != ENOENT) 343 { 344 ulog (LOG_ERROR, "opendir (%s): %s", zsub, 345 strerror (errno)); 346 ubuffree (zsub); 347 return FALSE; 348 } 349 ubuffree (zsub); 350 continue; 351 } 352 ubuffree (zsub); 353#endif 354 355 while ((qentry = readdir (qdir)) != NULL) 356 { 357 char bfilegrade; 358 char *zname; 359 struct ssfilename slook; 360 361#if ! SPOOLDIR_SVR4 362 zname = zbufcpy (qentry->d_name); 363#else 364 zname = zsysdep_in_dir (qgentry->d_name, qentry->d_name); 365 bfilegrade = qgentry->d_name[0]; 366#endif 367 368 slook.zfile = zname; 369 if (! fswork_file (qsys->uuconf_zname, qentry->d_name, 370 &bfilegrade) 371 || UUCONF_GRADE_CMP (bgrade, bfilegrade) < 0 372 || (asSwork_files != NULL 373 && bsearch ((pointer) &slook, 374 (pointer) asSwork_files, 375 chad, sizeof (struct ssfilename), 376 iswork_cmp) != NULL)) 377 ubuffree (zname); 378 else 379 { 380 DEBUG_MESSAGE1 (DEBUG_SPOOLDIR, 381 "fsysdep_get_work_init: Found %s", 382 zname); 383 384 if (cSwork_files >= callocated) 385 { 386 callocated += CWORKFILES; 387 asSwork_files = 388 ((struct ssfilename *) 389 xrealloc ((pointer) asSwork_files, 390 (callocated * sizeof (struct ssfilename)))); 391 } 392 393 asSwork_files[cSwork_files].zfile = zname; 394 asSwork_files[cSwork_files].bgrade = bfilegrade; 395 ++cSwork_files; 396 if (cmax != 0 && cSwork_files - chad > cmax) 397 break; 398 } 399 } 400 401#if SPOOLDIR_SVR4 402 closedir (qdir); 403 if (cmax != 0 && cSwork_files - chad > cmax) 404 break; 405 } 406 qdir = qgdir; 407#endif 408 409 closedir (qdir); 410 ubuffree (zdir); 411 412 /* Sorting the files alphabetically will get the grades in the 413 right order, since all the file prefixes are the same. */ 414 if (cSwork_files > iSwork_file) 415 qsort ((pointer) (asSwork_files + iSwork_file), 416 cSwork_files - iSwork_file, 417 sizeof (struct ssfilename), iswork_cmp); 418 419 return TRUE; 420} 421 422/* Get the next work entry for a system. This must parse the next 423 line in the next work file. The type of command is set into 424 qcmd->bcmd If there are no more commands, qcmd->bcmd is set to 'H'. 425 Each field in the structure is set to point to a spot in an 426 malloced string. The grade argument is never used; it has been 427 used by fsysdep_get_work_init. */ 428 429/*ARGSUSED*/ 430boolean 431fsysdep_get_work (qsys, bgrade, cmax, qcmd) 432 const struct uuconf_system *qsys; 433 int bgrade ATTRIBUTE_UNUSED; 434 unsigned int cmax ATTRIBUTE_UNUSED; 435 struct scmd *qcmd; 436{ 437 char *zdir; 438 439 if (qSwork_file != NULL && qSwork_file->cdid >= qSwork_file->clines) 440 qSwork_file = NULL; 441 442 if (asSwork_files == NULL) 443 { 444 qcmd->bcmd = 'H'; 445 return TRUE; 446 } 447 448 zdir = NULL; 449 450 /* This loop continues until a line is returned. */ 451 while (TRUE) 452 { 453 /* This loop continues until a file is opened and read in. */ 454 while (qSwork_file == NULL) 455 { 456 FILE *e; 457 struct ssfile *qfile; 458 int iline, callocated; 459 char *zline; 460 size_t cline; 461 char *zname; 462 char bfilegrade; 463 464 /* Read all the lines of a command file into memory. */ 465 do 466 { 467 if (iSwork_file >= cSwork_files) 468 { 469 qcmd->bcmd = 'H'; 470 ubuffree (zdir); 471 return TRUE; 472 } 473 474 if (zdir == NULL) 475 { 476 zdir = zswork_directory (qsys->uuconf_zname); 477 if (zdir == NULL) 478 return FALSE; 479 } 480 481 zname = zsysdep_in_dir (zdir, asSwork_files[iSwork_file].zfile); 482 bfilegrade = asSwork_files[iSwork_file].bgrade; 483 484 ++iSwork_file; 485 486 e = fopen (zname, "r"); 487 if (e == NULL) 488 { 489 ulog (LOG_ERROR, "fopen (%s): %s", zname, 490 strerror (errno)); 491 ubuffree (zname); 492 } 493 } 494 while (e == NULL); 495 496 qfile = (struct ssfile *) xmalloc (sizeof (struct ssfile)); 497 callocated = CFILELINES; 498 iline = 0; 499 500 zline = NULL; 501 cline = 0; 502 while (getline (&zline, &cline, e) > 0) 503 { 504 if (iline >= callocated) 505 { 506 /* The sizeof (struct ssfile) includes CFILELINES 507 entries already, so using callocated * sizeof 508 (struct ssline) will give us callocated * 509 CFILELINES entries. */ 510 qfile = 511 ((struct ssfile *) 512 xrealloc ((pointer) qfile, 513 (sizeof (struct ssfile) + 514 (callocated * sizeof (struct ssline))))); 515 callocated += CFILELINES; 516 } 517 qfile->aslines[iline].zline = zbufcpy (zline); 518 qfile->aslines[iline].qfile = NULL; 519 qfile->aslines[iline].ztemp = NULL; 520 iline++; 521 } 522 523 xfree ((pointer) zline); 524 525 if (fclose (e) != 0) 526 ulog (LOG_ERROR, "fclose: %s", strerror (errno)); 527 528 if (iline == 0) 529 { 530 /* There were no lines in the file; this is a poll file, 531 for which we return a 'P' command. */ 532 qfile->aslines[0].zline = zbufcpy ("P"); 533 qfile->aslines[0].qfile = NULL; 534 qfile->aslines[0].ztemp = NULL; 535 iline = 1; 536 } 537 538 qfile->zfile = zname; 539 qfile->bgrade = bfilegrade; 540 qfile->clines = iline; 541 qfile->cdid = 0; 542 qSwork_file = qfile; 543 } 544 545 /* This loop continues until all the lines from the current file 546 are used up, or a line is returned. */ 547 while (TRUE) 548 { 549 int iline; 550 551 if (qSwork_file->cdid >= qSwork_file->clines) 552 { 553 /* We don't want to free qSwork_file here, since it must 554 remain until all the lines have been completed. It 555 is freed in fsysdep_did_work. */ 556 qSwork_file = NULL; 557 /* Go back to the main loop which finds another file. */ 558 break; 559 } 560 561 iline = qSwork_file->cdid; 562 ++qSwork_file->cdid; 563 564 /* Now parse the line into a command. */ 565 if (! fparse_cmd (qSwork_file->aslines[iline].zline, qcmd)) 566 { 567 ulog (LOG_ERROR, "Bad line in command file %s", 568 qSwork_file->zfile); 569 ubuffree (qSwork_file->aslines[iline].zline); 570 qSwork_file->aslines[iline].zline = NULL; 571 continue; 572 } 573 qcmd->bgrade = qSwork_file->bgrade; 574 575 qSwork_file->aslines[iline].qfile = qSwork_file; 576 qcmd->pseq = (pointer) (&qSwork_file->aslines[iline]); 577 578 if (qcmd->bcmd == 'S' || qcmd->bcmd == 'E') 579 { 580 char *zreal; 581 582 zreal = zsysdep_spool_file_name (qsys, qcmd->ztemp, 583 qcmd->pseq); 584 if (zreal == NULL) 585 { 586 ubuffree (qSwork_file->aslines[iline].zline); 587 qSwork_file->aslines[iline].zline = NULL; 588 ubuffree (zdir); 589 return FALSE; 590 } 591 qSwork_file->aslines[iline].ztemp = zreal; 592 } 593 594 ubuffree (zdir); 595 return TRUE; 596 } 597 } 598} 599 600/* When a command has been complete, fsysdep_did_work is called. The 601 sequence entry was set above to be the address of an aslines 602 structure whose pfile entry points to the ssfile corresponding to 603 this file. We can then check whether all the lines have been 604 completed (they will have been if the pfile entry is NULL) and 605 remove the file if they have been. This means that we only remove 606 a command file if we manage to complete every transfer it specifies 607 in a single UUCP session. I don't know if this is how regular UUCP 608 works. */ 609 610boolean 611fsysdep_did_work (pseq) 612 pointer pseq; 613{ 614 struct ssfile *qfile; 615 struct ssline *qline; 616 int i; 617 618 qline = (struct ssline *) pseq; 619 620 ubuffree (qline->zline); 621 qline->zline = NULL; 622 623 qfile = qline->qfile; 624 qline->qfile = NULL; 625 626 /* Remove the temporary file, if there is one. It really doesn't 627 matter if this fails, and not checking the return value lets us 628 attempt to remove D.0 or whatever an unused temporary file is 629 called without complaining. */ 630 if (qline->ztemp != NULL) 631 { 632 (void) remove (qline->ztemp); 633 ubuffree (qline->ztemp); 634 qline->ztemp = NULL; 635 } 636 637 /* If not all the lines have been returned from fsysdep_get_work, 638 we can't remove the file yet. */ 639 if (qfile->cdid < qfile->clines) 640 return TRUE; 641 642 /* See whether all the commands have been completed. */ 643 for (i = 0; i < qfile->clines; i++) 644 if (qfile->aslines[i].qfile != NULL) 645 return TRUE; 646 647 /* All commands have finished. */ 648 if (remove (qfile->zfile) != 0) 649 { 650 ulog (LOG_ERROR, "remove (%s): %s", qfile->zfile, 651 strerror (errno)); 652 return FALSE; 653 } 654 655 ubuffree (qfile->zfile); 656 xfree ((pointer) qfile); 657 658 if (qfile == qSwork_file) 659 qSwork_file = NULL; 660 661 return TRUE; 662} 663 664/* Free up the results of a work scan, when we're done with this 665 system. */ 666 667/*ARGSUSED*/ 668void 669usysdep_get_work_free (qsys) 670 const struct uuconf_system *qsys ATTRIBUTE_UNUSED; 671{ 672 if (asSwork_files != NULL) 673 { 674 size_t i; 675 676 for (i = 0; i < cSwork_files; i++) 677 ubuffree ((pointer) asSwork_files[i].zfile); 678 xfree ((pointer) asSwork_files); 679 asSwork_files = NULL; 680 cSwork_files = 0; 681 iSwork_file = 0; 682 } 683 if (qSwork_file != NULL) 684 { 685 int i; 686 687 ubuffree (qSwork_file->zfile); 688 for (i = 0; i < qSwork_file->cdid; i++) 689 { 690 ubuffree (qSwork_file->aslines[i].zline); 691 ubuffree (qSwork_file->aslines[i].ztemp); 692 } 693 for (i = qSwork_file->cdid; i < qSwork_file->clines; i++) 694 ubuffree (qSwork_file->aslines[i].zline); 695 xfree ((pointer) qSwork_file); 696 qSwork_file = NULL; 697 } 698} 699 700/* Save the temporary file used by a send command, and return an 701 informative message to mail to the requestor. This is called when 702 a file transfer failed, to make sure that the potentially valuable 703 file is not completely lost. */ 704 705const char * 706zsysdep_save_temp_file (pseq) 707 pointer pseq; 708{ 709 struct ssline *qline = (struct ssline *) pseq; 710 char *zto, *zslash; 711 size_t cwant; 712 static char *zbuf; 713 static size_t cbuf; 714 715 if (! fsysdep_file_exists (qline->ztemp)) 716 return NULL; 717 718 zslash = strrchr (qline->ztemp, '/'); 719 if (zslash == NULL) 720 zslash = qline->ztemp; 721 else 722 ++zslash; 723 724 zto = zbufalc (sizeof PRESERVEDIR + sizeof "/" + strlen (zslash)); 725 sprintf (zto, "%s/%s", PRESERVEDIR, zslash); 726 727 if (! fsysdep_move_file (qline->ztemp, zto, TRUE, FALSE, FALSE, 728 (const char *) NULL)) 729 { 730 /* Leave the file where it was, not that is much help. */ 731 ubuffree (zto); 732 return "Could not move file to preservation directory"; 733 } 734 735 cwant = sizeof "File saved as\n\t/" + strlen (zSspooldir) + strlen (zto); 736 if (cwant > cbuf) 737 { 738 ubuffree (zbuf); 739 zbuf = zbufalc (cwant); 740 cbuf = cwant; 741 } 742 743 sprintf (zbuf, "File saved as\n\t%s/%s", zSspooldir, zto); 744 ubuffree (zto); 745 return zbuf; 746} 747 748/* Get the jobid of a work file. This is needed by uustat. */ 749 750char * 751zsysdep_jobid (qsys, pseq) 752 const struct uuconf_system *qsys; 753 pointer pseq; 754{ 755 return zsfile_to_jobid (qsys, ((struct ssline *) pseq)->qfile->zfile, 756 bsgrade (pseq)); 757} 758 759/* Get the grade of a work file. The pseq argument can be NULL when 760 this is called from zsysdep_spool_file_name, and simply means that 761 this is a remote file; returning -1 will cause zsfind_file to do 762 the right thing. */ 763 764int 765bsgrade (pseq) 766 pointer pseq; 767{ 768 const char *zfile; 769 char bgrade; 770 771 if (pseq == NULL) 772 return -1; 773 774 zfile = ((struct ssline *) pseq)->qfile->zfile; 775 776#if SPOOLDIR_TAYLOR 777 bgrade = *(strrchr (zfile, '/') + 3); 778#else 779#if ! SPOOLDIR_SVR4 780 bgrade = zfile[strlen (zfile) - CSEQLEN - 1]; 781#else 782 bgrade = *(strchr (zfile, '/') + 1); 783#endif 784#endif 785 786 return bgrade; 787} 788