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