/* config.c Read the configuration file. Copyright (C) 1991, 1992 Ian Lance Taylor This file is part of the Taylor UUCP package. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The author of the program may be contacted at ian@airs.com or c/o AIRS, P.O. Box 520, Waltham, MA 02254. $Log: config.c,v $ Revision 1.1.1.1 1993/03/21 09:45:37 cgd initial import of 386bsd-0.1 sources Revision 1.29 1992/03/28 22:06:38 ian Michael I Bushnell: renamed enum tstatus to avoid header file conflict Revision 1.28 1992/03/28 21:47:55 ian David J. MacKenzie: allow backslash to quote newline in config files Revision 1.27 1992/03/28 04:26:12 ian David J. MacKenzie: cMaxuuxqts is independent of HAVE_TAYLOR_CONFIG Revision 1.26 1992/03/18 23:12:37 ian Handle CMDTABTYPE_FULLSTRING correctly if there are no arguments Revision 1.25 1992/03/17 01:03:03 ian Miscellaneous cleanup Revision 1.24 1992/03/12 19:54:43 ian Debugging based on types rather than number Revision 1.23 1992/03/11 22:06:37 ian Marty Shannon: added max-uuxqts command Revision 1.22 1992/03/09 05:08:16 ian Added status for wrong time to call, not used if system can't be called Revision 1.21 1992/03/04 21:39:04 ian Local variables in igradecmp have to be integers Revision 1.20 1992/03/03 06:06:48 ian T. William Wells: don't complain about missing configuration files Revision 1.19 1992/02/29 01:06:59 ian Chip Salzenberg: recheck file permissions before sending Revision 1.18 1992/02/24 04:58:47 ian Only permit files to be received into directories that are world-writeable Revision 1.17 1992/02/08 03:54:18 ian Include only in , added 1992 copyright Revision 1.16 1992/01/15 07:06:29 ian Set configuration directory in Makefile rather than sysdep.h Revision 1.15 1992/01/14 04:04:17 ian Chip Salzenberg: strcmp is a macro on AIX Revision 1.14 1991/12/29 04:04:18 ian Added a bunch of extern definitions Revision 1.13 1991/12/29 01:54:46 ian Terry Gardner: allow a # character to be quoted in a configuration file Revision 1.12 1991/12/22 20:57:57 ian Added externs for strcasecmp or strncasecmp Revision 1.11 1991/12/18 03:54:14 ian Made error messages to terminal appear more normal Revision 1.10 1991/12/15 03:42:33 ian Added tprocess_chat_cmd for all chat commands, and added CMDTABTYPE_PREFIX Revision 1.9 1991/12/13 22:43:06 ian Don't continually allocate and free the list of arguments Revision 1.8 1991/12/09 18:39:46 ian Richard Todd: the pushed back line is specific to a particular multi file Revision 1.7 1991/12/09 16:59:48 ian Richard Todd: don't warn if special "#" command is unrecognized Revision 1.6 1991/12/07 18:05:20 ian Franc,ois Pinard: no limit to number of arguments Revision 1.5 1991/12/03 02:38:26 ian Don't treat numbers with leading zeroes as octal Revision 1.4 1991/12/01 19:35:38 ian David Nugent: read V2 and BNU files by default even with TAYLOR_CONFIG Revision 1.3 1991/11/26 02:04:49 ian Bob Denny: add explicit extern for strcmp and strcasecmp Revision 1.2 1991/09/19 02:38:21 ian Chip Salzenberg: V2 and BNU dialcodes files are relative to CONFIGLIB Revision 1.1 1991/09/10 19:38:34 ian Initial revision */ #include "uucp.h" #if USE_RCS_ID char config_rcsid[] = "$Id: config.c,v 1.1.1.1 1993/03/21 09:45:37 cgd Exp $"; #endif #include #include #include "system.h" #include "sysdep.h" /* Some systems make strcmp a macro, which screws us up since we want to declare it since we will take its address later. */ #undef strcmp #undef strncmp /* External functions. */ extern int strcmp (), strncmp (), strcasecmp (), strncasecmp (); extern int fclose (); /* Local functions. */ static void umulti_pushback P((struct smulti_file *, char *)); /* Status strings. These must match enum tstatus_type. */ const char *azStatus[] = { "Conversation complete", "Port unavailable", "Dial failed", "Login failed", "Handshake failed", "Call failed", "Talking", "Wrong time to call" }; /* Local node name. */ const char *zLocalname; /* Spool directory. */ const char *zSpooldir = SPOOLDIR; #if DEBUG > 1 /* Debugging level. */ int iDebug = 0; /* Debugging file name. */ const char *zDebugfile = DEBUGFILE; #endif /* Public directory. */ const char *zPubdir = PUBDIR; /* Log file name. */ const char *zLogfile = LOGFILE; /* Statistics file name. */ const char *zStatfile = STATFILE; /* Dialcode file names. */ char *zDialcodefile; /* The maximum number of uuxqt processes which may be running at one time. If this is zero, there is no limit. */ int cMaxuuxqts; #if HAVE_TAYLOR_CONFIG /* System file names. */ char *zSysfile; /* Port file names. */ char *zPortfile; /* Dialer file names. */ char *zDialfile; /* Call out login and password file names. */ char *zCallfile; /* Call in login and password file names. */ char *zPwdfile; /* Command table used to parse the configuration file. */ static enum tcmdtabret tcadd P((int argc, char **argv, pointer pvar, const char *zerr)); const struct scmdtab asCmds[] = { /* System name. */ { "nodename", CMDTABTYPE_STRING, (pointer) &zLocalname, NULL }, { "hostname", CMDTABTYPE_STRING, (pointer) &zLocalname, NULL }, { "uuname", CMDTABTYPE_STRING, (pointer) &zLocalname, NULL }, /* Spool directory. */ { "spool", CMDTABTYPE_STRING, (pointer) &zSpooldir, NULL }, /* System information files. */ { "sysfile", CMDTABTYPE_FN | 0, (pointer) &zSysfile, tcadd }, /* Port files. */ { "portfile", CMDTABTYPE_FN | 0, (pointer) &zPortfile, tcadd }, /* Dial files. */ { "dialfile", CMDTABTYPE_FN | 0, (pointer) &zDialfile, tcadd }, /* Dialcode files. */ { "dialcodefile", CMDTABTYPE_FN | 0, (pointer) &zDialcodefile, tcadd }, /* Public directory. */ { "pubdir", CMDTABTYPE_STRING, (pointer) &zPubdir, NULL }, /* Call out login name and password files. */ { "callfile", CMDTABTYPE_FN | 0, (pointer) &zCallfile, tcadd }, /* Call in login name and password files. */ { "passwdfile", CMDTABTYPE_FN | 0, (pointer) &zPwdfile, tcadd }, /* Log file. */ { "logfile", CMDTABTYPE_STRING, (pointer) &zLogfile, NULL }, /* Statistics file. */ { "statfile", CMDTABTYPE_STRING, (pointer) &zStatfile, NULL }, #if DEBUG > 1 /* Debugging file. */ { "debugfile", CMDTABTYPE_STRING, (pointer) &zDebugfile, NULL }, /* Debugging level. */ { "debug", CMDTABTYPE_FN | 0, (pointer) &iDebug, tidebug_parse }, #endif /* Command for unknown system. */ { "unknown", CMDTABTYPE_FN, NULL, tiunknown }, /* Maximum number of uuxqt processes. */ { "max-uuxqts", CMDTABTYPE_INT, (pointer) &cMaxuuxqts, NULL }, #if HAVE_V2_CONFIG { "v2-files", CMDTABTYPE_BOOLEAN, (pointer) &fV2, NULL }, #endif #if HAVE_BNU_CONFIG { "bnu-files", CMDTABTYPE_BOOLEAN, (pointer) &fBnu, NULL }, #endif /* End marker. */ { NULL, 0, NULL, NULL } }; /* Add strings to a string variable, separating with spaces. */ /*ARGSUSED*/ static enum tcmdtabret tcadd (argc, argv, pvar, zerr) int argc; char **argv; pointer pvar; const char *zerr; { int i; for (i = 1; i < argc; i++) uadd_string ((char **) pvar, argv[i], ' '); return CMDTABRET_FREE; } #endif /* HAVE_TAYLOR_CONFIG */ /* Process commands from a stdio file according to a command table. The character '#' introduces a comment. Exactly one of e and qmulti must be NULL. If we get the first line of a multi file, we attempt to process the command "#"; this command will not arise from a normal command file, and lets the caller take special action at the start of a new file. If we are reading with zmulti_gets, we ignore the zerr argument and instead use the name of the file. */ void uprocesscmds (e, qmulti, qcmds, zerr, iflags) FILE *e; struct smulti_file *qmulti; const struct scmdtab *qcmds; const char *zerr; int iflags; { while (TRUE) { static char **pzargs; static int calloc_args; char *zget, *z; int cargs; enum tcmdtabret t; char **pzhold_args; int chold_alloc_args; if (e != NULL) zget = zfgets (e, (iflags & CMDFLAG_BACKSLASH) != 0); else { boolean ffirst; zget = zmulti_gets (qmulti, &ffirst, &zerr, (iflags & CMDFLAG_BACKSLASH) != 0); if (zget != NULL && ffirst) { char *zargs; zargs = (char *) "#"; t = tprocess_one_cmd (1, &zargs, qcmds, zerr, iflags &~ CMDFLAG_WARNUNRECOG); if (t == CMDTABRET_EXIT || t == CMDTABRET_FREE_AND_EXIT) { umulti_pushback (qmulti, zget); return; } } } if (zget == NULL) return; /* Any # character not preceeded by a backslash starts a comment. */ z = zget; while ((z = strchr (z, '#')) != NULL) { if (z == zget || *(z - 1) != '\\') { *z = '\0'; break; } /* Remove the backslash. */ xmemmove (z - 1, z, strlen (z) + 1); } z = zget; cargs = 0; while (TRUE) { while (*z != '\0' && isspace (BUCHAR (*z))) z++; if (*z == '\0') break; if (cargs >= calloc_args) { calloc_args += 10; pzargs = (char **) xrealloc ((pointer) pzargs, calloc_args * sizeof (char *)); } pzargs[cargs] = z; ++cargs; while (*z != '\0' && ! isspace (BUCHAR (*z))) z++; if (*z == '\0') break; *z++ = '\0'; } if (cargs <= 0) { xfree ((pointer) zget); continue; } /* Save off the static variables to allow this function to be called recursively. */ pzhold_args = pzargs; chold_alloc_args = calloc_args; pzargs = NULL; calloc_args = 0; t = tprocess_one_cmd (cargs, pzhold_args, qcmds, zerr, iflags); if (pzargs != NULL) xfree ((pointer) pzargs); pzargs = pzhold_args; calloc_args = chold_alloc_args; if (t == CMDTABRET_FREE || t == CMDTABRET_FREE_AND_EXIT) xfree ((pointer) zget); if (t == CMDTABRET_EXIT || t == CMDTABRET_FREE_AND_EXIT) return; } } /* Process a single command. */ enum tcmdtabret tprocess_one_cmd (cargs, azargs, qcmds, zerr, iflags) int cargs; char **azargs; const struct scmdtab *qcmds; const char *zerr; int iflags; { const struct scmdtab *q; int (*pfcmp) P((const char *, const char *)); if ((iflags & CMDFLAG_CASESIGNIFICANT) != 0) pfcmp = strcmp; else pfcmp = strcasecmp; for (q = qcmds; q->zcmd != NULL; q++) { int itype; int callowed; itype = TTYPE_CMDTABTYPE (q->itype); if (itype != CMDTABTYPE_PREFIX) { if ((*pfcmp) (q->zcmd, azargs[0]) != 0) continue; } else { int clen; clen = strlen (q->zcmd); if ((iflags & CMDFLAG_CASESIGNIFICANT) != 0) { if (strncmp (q->zcmd, azargs[0], clen) != 0) continue; } else { if (strncasecmp (q->zcmd, azargs[0], clen) != 0) continue; } } callowed = CARGS_CMDTABTYPE (q->itype); if (callowed != 0 && callowed != cargs) { if (zerr != NULL) ulog (LOG_ERROR, "%s: %s: Wrong number of arguments", zerr, q->zcmd); return CMDTABRET_FREE; } else if (itype == TTYPE_CMDTABTYPE (CMDTABTYPE_STRING)) { if (cargs != 1 && cargs != 2) { if (zerr != NULL) ulog (LOG_ERROR, "%s: %s: Wrong number of arguments", zerr, q->zcmd); return CMDTABRET_FREE; } if (cargs == 1) *(const char **) q->pvar = ""; else *(const char **) q->pvar = azargs[1]; return CMDTABRET_CONTINUE; } else if (itype == TTYPE_CMDTABTYPE (CMDTABTYPE_INT) || itype == TTYPE_CMDTABTYPE (CMDTABTYPE_LONG)) { long i; char *zend; i = strtol (azargs[1], &zend, 10); if (*zend != '\0') { if (zerr != NULL) ulog (LOG_ERROR, "%s: %s: Bad number", zerr, q->zcmd); return CMDTABRET_FREE; } if (itype == TTYPE_CMDTABTYPE (CMDTABTYPE_INT)) * (int *) q->pvar = (int) i; else * (long *) q->pvar = i; return CMDTABRET_FREE; } else if (itype == TTYPE_CMDTABTYPE (CMDTABTYPE_BOOLEAN)) { char b; b = azargs[1][0]; if (b == 'y' || b == 'Y' || b == 't' || b == 'T') * (boolean *) q->pvar = TRUE; else if (b == 'n' || b == 'N' || b == 'f' || b == 'F') * (boolean *) q->pvar = FALSE; else if (zerr != NULL) ulog (LOG_ERROR, "%s: %s: Bad boolean", zerr, q->zcmd); return CMDTABRET_FREE; } else if (itype == TTYPE_CMDTABTYPE (CMDTABTYPE_FULLSTRING)) { int i, clen; char *zset; /* Use all the arguments separated by a ' ' character. */ clen = 1; for (i = 1; i < cargs; i++) clen += strlen (azargs[i]) + 1; zset = (char *) xmalloc (clen); *zset = '\0'; for (i = 1; i < cargs - 1; i++) { strcat (zset, azargs[i]); strcat (zset, " "); } if (i < cargs) strcat (zset, azargs[i]); * (const char **) q->pvar = zset; return CMDTABRET_FREE; } else if (itype == TTYPE_CMDTABTYPE (CMDTABTYPE_FN) || itype == TTYPE_CMDTABTYPE (CMDTABTYPE_PREFIX)) return (*q->ptfn) (cargs, azargs, q->pvar, zerr); else { #if DEBUG > 0 ulog (LOG_FATAL, "tprocess_one_cmd: Can't happen (0x%x)", itype); #endif return CMDTABRET_FREE; } } if ((iflags & CMDFLAG_WARNUNRECOG) != 0 && zerr != NULL) ulog (LOG_ERROR, "%s: Unrecognized command %s", zerr, azargs[0]); return CMDTABRET_FREE; } /* Read the configuration file. If we can't open the configuration file, and we're trying to read the default, don't report an error. This permits people to not have a configuration file at all if they are satisfied with the compiled in defaults. */ void uread_config (zname) const char *zname; { #if HAVE_TAYLOR_CONFIG FILE *e; char *zdefault; zdefault = (char *) alloca (sizeof NEWCONFIGLIB + sizeof CONFIGFILE); sprintf (zdefault, "%s%s", NEWCONFIGLIB, CONFIGFILE); /* On UNIX we will be probably be running suid to uucp. We don't want to let somebody run us and specify some arbitrary file as the configuration file, since that might let them examine files they have no access to. */ if (zname != NULL && strcmp (zname, zdefault) != 0 && ! fsysdep_other_config (zname)) { ulog (LOG_ERROR, "Can't read %s; using %s", zname, zdefault); zname = zdefault; } if (zname == NULL) zname = zdefault; e = fopen (zname, "r"); if (e == NULL) { if (strcmp (zname, zdefault) != 0) { fprintf (stderr, "%s: %s: %s\n", abProgram, zname, strerror (errno)); /* We haven't yet called usysdep_initialize, so it should be safe to just exit. */ exit (EXIT_FAILURE); } /* Just use defaults. */ } else { uiunknown_start (); uprocesscmds (e, (struct smulti_file *) NULL, asCmds, zname, CMDFLAG_BACKSLASH); (void) fclose (e); uiunknown_end (); } #else /* ! HAVE_TAYLOR_CONFIG */ uiunknown_start (); uiunknown_end (); #endif /* ! HAVE_TAYLOR_CONFIG */ if (zLocalname == NULL) { zLocalname = zsysdep_local_name (); if (zLocalname == NULL) { fprintf (stderr, "%s: Can't get local node name\n", abProgram); exit (EXIT_FAILURE); } } uisetup_localsys (); #if HAVE_TAYLOR_CONFIG /* Get the defaults for the file names. */ #define SETDEFAULT(z, zfile) \ if (z == NULL) \ { \ z = (char *) xmalloc (sizeof NEWCONFIGLIB + sizeof zfile - 1); \ strcpy (z, NEWCONFIGLIB); \ strcat (z, zfile); \ if (! fsysdep_file_exists (z)) \ { \ xfree ((pointer) z); \ z = NULL; \ } \ } SETDEFAULT (zSysfile, SYSFILE); SETDEFAULT (zPortfile, PORTFILE); SETDEFAULT (zDialfile, DIALFILE); SETDEFAULT (zDialcodefile, DIALCODEFILE); SETDEFAULT (zPwdfile, PASSWDFILE); SETDEFAULT (zCallfile, CALLFILE); #endif /* HAVE_TAYLOR_CONFIG */ #if HAVE_BNU_CONFIG /* If we are supposed to read standard BNU files, read Sysfiles to get any nonstandard file names. */ if (fBnu) ubnu_read_sysfiles (); #endif /* HAVE_BNU_CONFIG */ /* The format of the dialcodes file is the same for all systems, so if additional configuration are being used we add the files in here to save having to do it when the dialcodes file is read. */ #if HAVE_V2_CONFIG if (fV2) { char *z; z = (char *) alloca (sizeof OLDCONFIGLIB + sizeof V2_DIALCODES); sprintf (z, "%s%s", OLDCONFIGLIB, V2_DIALCODES); uadd_string (&zDialcodefile, z, ' '); } #endif /* HAVE_V2_CONFIG */ #if HAVE_BNU_CONFIG if (fBnu) { char *z; z = (char *) alloca (sizeof OLDCONFIGLIB + sizeof BNU_DIALCODES); sprintf (z, "%s%s", OLDCONFIGLIB, BNU_DIALCODES); uadd_string (&zDialcodefile, z, ' '); } #endif /* HAVE_BNU_CONFIG */ } /* Add a string to a list of strings separated by a separator character. */ void uadd_string (pz, z, bsep) char **pz; const char *z; int bsep; { if (*pz == NULL) *pz = xstrdup (z); else { int clen; clen = strlen (*pz); *pz = (char *) xrealloc ((pointer) *pz, clen + strlen (z) + 2); (*pz)[clen] = (char) bsep; strcpy (*pz + clen + 1, z); } } #if DEBUG > 1 /* Parse a debugging string. This may be a simple number, which sets the given number of bits in iDebug, or it may be a series of single letters. */ static const char * const azDebug_names[] = DEBUG_NAMES; int idebug_parse (z) const char *z; { char *zend; int i, iret; char *zcopy, *ztok; i = (int) strtol (z, &zend, 0); if (*zend == '\0') { if (i > 15) i = 15; else if (i < 0) i = 0; return (1 << i) - 1; } zcopy = (char *) alloca (strlen (z) + 1); strcpy (zcopy, z); iret = 0; for (ztok = strtok (zcopy, ","); ztok != NULL; ztok = strtok ((char *) NULL, ",")) { if (strcasecmp (ztok, "all") == 0) { iret = DEBUG_MAX; break; } for (i = 0; azDebug_names[i] != NULL; i++) { if (strncasecmp (ztok, azDebug_names[i], strlen (azDebug_names[i])) == 0) { iret |= 1 << i; break; } } if (azDebug_names[i] == NULL) ulog (LOG_ERROR, "Unrecognized debugging option \"%s\"", ztok); } return iret; } /* Parse a debugging string in a configuration file. The pvar arguments points to the field to set. */ /*ARGSUSED*/ enum tcmdtabret tidebug_parse (argc, argv, pvar, zerr) int argc; char **argv; pointer pvar; const char *zerr; { int *pidebug = (int *) pvar; int i; if (argc == 2 && strncasecmp (argv[1], DEBUG_NONE, strlen (DEBUG_NONE)) == 0) { *pidebug = 0; return CMDTABRET_FREE; } for (i = 1; i < argc; i++) *pidebug |= idebug_parse (argv[i]); return CMDTABRET_FREE; } #endif /* DEBUG > 1 */ /* Given a file name that may actually be several file names each separated with a single space character, we want to be able to read lines from them as though they were catenated to form a single file. This set of routines allows us to do that easily, although only for one set of files at a time. One line can be pushed back, which can be convenient for the first line of a file. */ struct smulti_file { /* Names of next files to open. */ char *z; /* String to free up when done. */ char *zfree; /* Current file. */ FILE *e; /* Current file name. */ char *zname; /* Next line to return. */ char *znext; }; struct smulti_file * qmulti_open (znames) const char *znames; { struct smulti_file *qret; qret = (struct smulti_file *) xmalloc (sizeof (struct smulti_file)); qret->z = qret->zfree = xstrdup (znames); qret->zname = NULL; qret->e = NULL; qret->znext = NULL; return qret; } /* Read the next line from a multiply opened set of files. */ char * zmulti_gets (q, pffirst, pzname, fbackslash) struct smulti_file *q; boolean *pffirst; const char **pzname; boolean fbackslash; { if (pffirst != NULL) *pffirst = FALSE; if (q->znext != NULL) { char *z; z = q->znext; q->znext = NULL; return z; } while (TRUE) { char *z; if (q->e != NULL) { char *zret; zret = zfgets (q->e, fbackslash); if (zret != NULL) { if (pzname != NULL) *pzname = q->zname; return zret; } if (fclose (q->e) != 0) { ulog (LOG_ERROR, "fclose: %s", strerror (errno)); q->e = NULL; return NULL; } q->e = NULL; } if (*q->z == '\0') return NULL; z = q->z; q->z += strcspn (z, " "); if (*q->z != '\0') *q->z++ = '\0'; q->e = fopen (z, "r"); if (q->e == NULL) { ulog (LOG_ERROR, "fopen (%s): %s", z, strerror (errno)); return NULL; } if (pffirst != NULL) *pffirst = TRUE; q->zname = z; } } /* Push back a line so that it is read by the next call to zmulti_gets. This is sometimes used on the first line of a file in uprocesscmds. */ static void umulti_pushback (q, z) struct smulti_file *q; char *z; { #if DEBUG > 0 if (q->znext != NULL) ulog (LOG_FATAL, "umulti_pushback: Can't happen"); #endif q->znext = z; } /* Close the multiple file, even though a read is in progress. If zmulti_gets returns NULL, the files will have been closed. */ boolean fmulti_close (q) struct smulti_file *q; { boolean fret; fret = TRUE; if (q->e != NULL) { if (fclose (q->e) != 0) { ulog (LOG_ERROR, "fclose: %s", strerror (errno)); fret = FALSE; } } xfree ((pointer) q->zfree); xfree ((pointer) q); return fret; } /* See whether a file is in a directory list, and make sure the user has appropriate access. */ boolean fin_directory_list (qsys, zfile, zdirs, fcheck, freadable, zuser) const struct ssysteminfo *qsys; const char *zfile; const char *zdirs; boolean fcheck; boolean freadable; const char *zuser; { char *zcopy; boolean fmatch; zcopy = (char *) alloca (strlen (zdirs) + 1); strcpy (zcopy, zdirs); fmatch = FALSE; while (*zcopy != '\0') { char *z; z = zcopy + strcspn (zcopy, " "); if (*z == '\0') --z; else *z = '\0'; if (*zcopy == '!') { if (fsysdep_in_directory (qsys, zfile, zcopy + 1, FALSE, FALSE, (const char *) NULL)) fmatch = FALSE; } else { if (fsysdep_in_directory (qsys, zfile, zcopy, fcheck, freadable, zuser)) fmatch = TRUE; } zcopy = z + 1; } return fmatch; } /* See whether a file is a spool file. Spool file names are specially crafted to hand around to other UUCP packages. They always begin with 'C', 'D' or 'X', and the second character is always a period. The remaining characters are any character that could appear in a system name. */ boolean fspool_file (zfile) const char *zfile; { const char *z; if (*zfile != 'C' && *zfile != 'D' && *zfile != 'X') return FALSE; if (zfile[1] != '.') return FALSE; for (z = zfile + 2; *z != '\0'; z++) if (! isalnum (BUCHAR (*z)) && *z != '_' && *z != '-' && *z != '.') return FALSE; return TRUE; } /* Compare two grades, returning < 0 if the first should be executed before the second, == 0 if they are the same, and > 0 if the first should be executed after the second. This code assumes that the upper case letters appear in sequence and the lower case letters appear in sequence. */ int igradecmp (barg1, barg2) int barg1; int barg2; { int b1, b2; /* Make sure the arguments are unsigned. */ b1 = (int) BUCHAR (barg1); b2 = (int) BUCHAR (barg2); if (isdigit (b1)) { if (isdigit (b2)) return b1 - b2; else return -1; } else if (isupper (b1)) { if (isdigit (b2)) return 1; else if (isupper (b2)) return b1 - b2; else return -1; } else { if (! islower (b2)) return 1; else return b1 - b2; } }