#ifndef lint static char sccsid[] = "@(#)rpc←main.c 1.1 90/10/29 (C) 1987 SMI"; #endif /* * rpc←main.c, Top level of the RPC protocol compiler. * Copyright (C) 1987, Sun Microsystems, Inc. */ #include <stdio.h> #include <strings.h> #include <string.h> #include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include "rpc←util.h" #include "rpc←parse.h" #include "rpc←scan.h" #define EXTEND 1 /* alias for TRUE */ struct commandline { int cflag; /* xdr C routines */ int hflag; /* header file */ int lflag; /* client side stubs */ int sflag; /* server stubs for the given transport */ char *infile; /* input module name */ char *outfile; /* output module name */ }; static char *cmdname; static char CPP[] = "/lib/cpp"; static char CPPFLAGS[] = "-C"; int PFlag = FALSE; /* Strip prefix module names */ int ExpandProcArgs = FALSE; /* TRUE => expand proc struct parameters */ int RecoveryFlag = FALSE; /* TRUE => add RPC.Failed recovery loop to client stubs and RecoveryProc parm. to client import routines. */ char *svcName; /* Name of service being compiled. */ char *ServiceName(); void MakeSymbolTables(); int BadProgNameFlag[32]; /* Array of flags indicating if a program defn. has a name that can't be translated to Cedar by the standard means when prefix stripping being done. The nth flag corresponds to the nth program defn. TRUE => Need to use an altered form of progName with prefix stripping. */ int CurrentProgNameFlag = -1; /* Index in BadProgNameFlag for the program defn. currently being processed. */ int PCedar = 0; /* Flag to signal that PCedar2.0 is the target environment. */ int PCedarUDP = 0; /* Flag to signal that SunRPC should be used. */ int PCedarTCP = 0; /* Flag to signal that SunRPCStream should be used. */ char *SunRPC = "SunRPC"; /* Name of SunRPC module to use. For PCedarTCP, the name to use will be SunRPCStream. */ int SeqTypeId = 0; /* Global constant used to create unique names for sequence types we need to define. */ Context *CurrentContext = NULL; int TypeDefsExist = FALSE; /* Used to signal if an XDR file should exist for the .x file. */ int ProgramsExist = FALSE; /* Used to signal if a program is defined in the .x file. */ enum {noFile, svcInterface, clientImpl, serverImpl, xdrInterface, xdrImpl} OutFileType; list *Imports; /* List of IMPORT module names. */ list *GetPutImports; /* List of IMPORT GetPut module names. */ StringHashTableHandle ImportNames; /* Hash table of imported names. Hash table elem values are module names. */ /* IMPORTed .x files are searched for on a path defined by an environment var */ #define PATH←ENV←VAR "DOTXPATH" #define PATH←DEFAULT ".:/project/ubi/src/x" StringHashTableHandle SymbolNames; /* Hash table of struct definitions. Used to decide whether or not to expand procedure input parameters. */ StringHashTableHandle SeqTypes; /* Hash table of Cedar sequence types for variable-length arrays. */ StringHashTableHandle EnumTypes; /* Hash table of enum types. Contains lists of their element names. */ main(argc, argv) int argc; char *argv[]; { struct commandline cmd; char *interfaceName; char *getPutName; char *getPutImplName; char *clientImplName; char *serverImplName; char *extendfile(); bzero((char *)&cmd, sizeof (struct commandline)); if (!parseargs(argc, argv, &cmd)) { f←print(stderr, "usage: %s [-p] [-e] infile\t\t-- generates Cedar10.0 stubs\n", cmdname); f←print(stderr, "\t%s [-p] [-e] [-PCedar] infile\t\t-- generates PCedar UDP stubs\n", cmdname); f←print(stderr, "\t%s [-p] [-e] [-PCedarStream] infile\t\t-- generates PCedar TCP stubs\n", cmdname); exit(1); } CurrentContext = (Context *) malloc(sizeof(Context)); reinitialize(); svcName = ServiceName(cmd.infile); MakeSymbolTables(cmd.infile); if (cmd.cflag || cmd.hflag || cmd.lflag || cmd.sflag) { fprintf(stderr, "options not available for Cedar\n"); exit(-1); } else { /* the rescans are required, since cpp may effect input */ interfaceName = extendfile(cmd.infile, ""); getPutName = extendfile(cmd.infile, "GetPut"); getPutImplName = extendfile(cmd.infile, "GetPutImpl"); clientImplName = extendfile(cmd.infile, "ClientImpl"); serverImplName = extendfile(cmd.infile, "ServerImpl"); if (PCedar && PCedarTCP) { SunRPC = "SunRPCStream"; } /* *** Note *** h←output must be called before the others. */ h←output(cmd.infile, interfaceName, "-DRPC←HDR"); reinitialize(); c←output(cmd.infile, getPutName, getPutImplName, "-DRPC←XDR"); reinitialize(); l←output(cmd.infile, clientImplName, "-DRPC←CLNT"); reinitialize(); s←output(cmd.infile, serverImplName, "-DRPC←SVC"); reinitialize(); } exit(0); /* NOTREACHED */ } /* * add extension to filename */ static char * extendfile(file, ext) char *file; char *ext; { char *res; char *p; res = alloc(strlen(file) + strlen(ext) + 1); if (res == NULL) { abort(); } p = rindex(file, '.'); if (p == NULL) { p = file + strlen(file); } (void) strcpy(res, file); (void) strcpy(res + (p - file), ext); return (res); } /* * Return the basename of a file. */ static char *ServiceName(filename) char *filename; { char *s; char *p; char *svcName = alloc(strlen(filename)+1); p = rindex(filename, '/'); if (p != NULL) s = p + 1; else s = filename; strcpy(svcName, s); p = rindex(svcName, '.'); if (p != NULL) { *p = '\0'; } return (svcName); } /* * Open output file with given extension */ static open←output(infile, outfile) char *infile; char *outfile; { if (outfile == NULL) { fout = stdout; EmitNotice(); return; } if (infile != NULL && streq(outfile, infile)) { f←print(stderr, "%s: output would overwrite %s\n", cmdname, infile); crash(); } if (access(outfile, F←OK) == 0) { (void )unlink(outfile); /* Get rid of the old version; which - if it's a symbolic link - would cause us problems during subsequent writing. */ } fout = fopen(outfile, "w"); if (fout == NULL) { f←print(stderr, "%s: unable to open ", cmdname); perror(outfile); crash(); } record←open(outfile); f←print(fout, "-- %s\n", outfile); EmitNotice(); } static EmitNotice() { f←print(fout, "-- Please do not edit this file.\n"); f←print(fout, "-- It was generated using CedarRPCGen.\n\n"); } /* * FIndFileOnPath */ static char * FindFileOnPath (name, varname, defaultpath) char *name; /* Filename to find */ char *varname; /* Name of env var that contains path */ char *defaultpath; /* Default path if no environment variable */ { char *prefix; char *getenv(); char *copy; char buf[1000]; char *base, *p; if (*name == '.' || *name == '/') { return (strdup(name)); } if ((prefix = getenv (varname)) == NULL) { prefix = defaultpath; } copy = strdup(prefix); base = copy; do { p = strchr (base, ':'); if (p != NULL) *p = (char) 0; sprintf (buf, "%s/%s", base, name); if (access (buf, R←OK) == 0) { p = strdup (buf); free (copy); return (p); } if (p != NULL && *(p+1) != (char) 0) { base = p + 1; } else { base = NULL; } } while (base != NULL); free (copy); return (NULL); } /* * Open input file with given define for C-preprocessor */ static open←input(infile, define) char *infile; char *define; { int pd[2]; if (infile == NULL) { CurrentContext->infilename = "<stdin>"; } else { CurrentContext->infilename = FindFileOnPath(infile, PATH←ENV←VAR, PATH←DEFAULT); if (CurrentContext->infilename != NULL) { infile = CurrentContext->infilename; } } (void) pipe(pd); switch (fork()) { case 0: (void) close(1); (void) dup2(pd[1], 1); (void) close(pd[0]); if (define != NULL && define[0] != '\0') execl(CPP, CPP, CPPFLAGS, define, infile, NULL); else execl(CPP, CPP, CPPFLAGS, infile, NULL); perror("execv"); exit(1); case -1: perror("fork"); exit(1); } (void) close(pd[1]); CurrentContext->fin = fdopen(pd[0], "r"); if (CurrentContext->fin == NULL) { f←print(stderr, "%s: ", cmdname); perror(CurrentContext->infilename); crash(); } } /* * Compile into a marshalling routines output file */ static c←output(infile, getputFile, getputImplFile, define) char *infile; char *getputFile, *getputImplFile; char *define; { char outputFileName[128]; definition *def; char *include; long tell; list *l; /* * Emit the interface file. */ sleep(1); /* Do this to avoid having the same creation time on two different files - something that Cedar can't handle. */ OutFileType = xdrInterface; open←input(infile, define); sprintf(outputFileName, "%s.mesa", getputFile); open←output(infile, outputFileName); f←print(fout, "DIRECTORY\n"); f←print(fout, " %s,\n", SunRPC); f←print(fout, " %s;\n\n", svcName); f←print(fout, "%s: CEDAR DEFINITIONS =\n", getputFile); f←print(fout, "BEGIN\n\n"); f←print(fout, "Handle: TYPE = %s.Handle;\n\n", SunRPC); tell = ftell(fout); while (def = get←definition()) { emitDefn(def); } if (tell == ftell(fout)) { (void) unlink(outputFileName); return; } f←print(fout, "END.\n"); fclose(fout); /* * Emit the module file. */ sleep(1); /* Do this to avoid having the same creation time on two different files - something that Cedar can't handle. */ OutFileType = xdrImpl; open←input(infile, define); sprintf(outputFileName, "%s.mesa", getputImplFile); open←output(infile, outputFileName); f←print(fout, "DIRECTORY\n"); if (PCedar) f←print(fout, " Basics,\n"); f←print(fout, " Rope,\n"); f←print(fout, " %s,\n", SunRPC); if (PCedarUDP) f←print(fout, " SunRPCNumbers,\n"); if (Imports != NULL) { for (l = Imports; l != NULL; l = l->next) { f←print(fout, " %s,\n", l->val); } } if (GetPutImports != NULL) { for (l = GetPutImports; l != NULL; l = l->next) { f←print(fout, " %s,\n", l->val); } } f←print(fout, " %s,\n", svcName); f←print(fout, " %sGetPut;\n\n", svcName); f←print(fout, "%s: CEDAR PROGRAM\n", getputImplFile); f←print(fout, " IMPORTS %s", SunRPC); if (PCedarUDP) f←print(fout, ", SunRPCNumbers"); if (GetPutImports != NULL) { for (l = GetPutImports; l != NULL; l = l->next) { f←print(fout, ", %s", l->val); } } f←print(fout, " EXPORTS %s, %sGetPut =\n", svcName, svcName); f←print(fout, "BEGIN\n"); f←print(fout, "Handle: TYPE = %s.Handle;\n\n", SunRPC); f←print(fout, "ROPE: TYPE = Rope.ROPE;\n\n"); tell = ftell(fout); while (def = get←definition()) { emitImpl(def); } if (tell == ftell(fout)) { (void) unlink(outputFileName); return; } f←print(fout, "END.\n"); fclose(fout); } /* * Compile into an M3 interface file for the service. */ static h←output(infile, outfile, define) char *infile; char *outfile; char *define; { definition *def; char outfilename[128]; list *l; sleep(1); /* Do this to avoid having the same creation time on two different files - something that Cedar can't handle. */ OutFileType = svcInterface; SeqTypeId = 0; /* Get open connection to (C-preprocessed) input file and an open output file. */ open←input(infile, define); sprintf(outfilename, "%s.mesa", outfile); open←output(infile, outfilename); /* Emit preamble. */ f←print(fout, "DIRECTORY\n"); f←print(fout, " Rope,\n"); f←print(fout, " Arpa,\n"); if (Imports != NULL) { for (l = Imports; l != NULL; l = l->next) f←print(fout, " %s,\n", l->val); } f←print(fout, " SunRPCAuth,\n"); if (PCedar) { if (PCedarUDP) f←print(fout, " SunRPC;\n\n"); else /* PCedarTCP */ f←print(fout, " SunRPCStream;\n\n"); } else { f←print(fout, " %s;\n\n", SunRPC); } f←print(fout, "%s: CEDAR DEFINITIONS =\n", svcName); f←print(fout, "BEGIN\n\n"); f←print(fout, "ROPE: TYPE = Rope.ROPE;\n\n"); /* Emit definitions. */ while (def = get←definition()) { print←datadef(def); } /* Print postamble. */ f←print(fout, "\nEND."); fclose(fout); } /* * Compile into an RPC service */ static s←output(infile, outfile, define) char *infile; char *outfile; char *define; { definition *def; char outfilename[128]; version←list *vers; proc←list *proc; list *l; long tell; sleep(1); /* Do this to avoid having the same creation time on two different files - something that Cedar can't handle. */ OutFileType = serverImpl; open←input(infile, define); sprintf(outfilename, "%s.mesa", outfile); open←output(infile, outfilename); f←print(fout, "DIRECTORY\n"); if (PCedarUDP) f←print(fout, " Basics,\n"); if (PCedarTCP) f←print(fout, " Convert,\n"); f←print(fout, " %s,\n", SunRPC); if (PCedarUDP) f←print(fout, " SunRPCNumbers,\n"); if (PCedar) { f←print(fout, " SunPMap,\n"); f←print(fout, " SunPMapLocal,\n"); } f←print(fout, " SunRPCAuth,\n"); f←print(fout, " Rope,\n"); if (Imports != NULL) { for (l = Imports; l != NULL; l = l->next) f←print(fout, " %s,\n", l->val); } if (GetPutImports != NULL) { for (l = GetPutImports; l != NULL; l = l->next) f←print(fout, " %s,\n", l->val); } if (TypeDefsExist) f←print(fout, " %sGetPut,\n", svcName); f←print(fout, " %s;\n\n", svcName); f←print(fout, "%s: CEDAR PROGRAM\n", outfile); if (PCedarUDP) f←print(fout, " IMPORTS Basics, Rope, SunPMapLocal, %s, SunRPCNumbers", SunRPC); else if (PCedarTCP) f←print(fout, " IMPORTS Convert, Rope, SunPMapLocal, %s", SunRPC); else f←print(fout, " IMPORTS Rope, %s", SunRPC); if (GetPutImports != NULL) { for (l = GetPutImports; l != NULL; l = l->next) f←print(fout, ", %s", l->val); } if (TypeDefsExist) f←print(fout, ", %sGetPut\n", svcName); else f←print(fout, "\n"); f←print(fout, " EXPORTS %s =\n", svcName); f←print(fout, "BEGIN\n"); f←print(fout, "ROPE: TYPE = Rope.ROPE;\n\n"); f←print(fout, "Handle: TYPE = %s.Handle;\n", SunRPC); f←print(fout, "Conversation: TYPE = SunRPCAuth.Conversation;\n\n"); f←print(fout, "defaultReplyTTL: CARDINAL ← 10;\n\n"); tell = ftell(fout); while (def = get←definition()) { if (def->def←kind == DEF←PROGRAM) { for (vers = def->def.pr.versions; vers != NULL; vers = vers->next) { write←server←prog(svcName, def->def←name, vers); } } } if (tell == ftell(fout)) { (void) unlink(outfilename); return; } f←print(fout, "END.\n"); fclose(fout); } /* * generate client side stubs */ static l←output(infile, outfile, define) char *infile; char *outfile; char *define; { char *include; definition *def; char outfilename[128]; int foundprogram = 0; list *l; sleep(1); /* Do this to avoid having the same creation time on two different files - something that Cedar can't handle. */ OutFileType = clientImpl; open←input(infile, define); sprintf(outfilename, "%s.mesa", outfile); open←output(infile, outfilename); f←print(fout, "DIRECTORY\n"); if (PCedar) f←print(fout, " Basics,\n"); if (PCedarTCP) f←print(fout, " Convert,\n"); f←print(fout, " Rope,\n"); f←print(fout, " SunRPC,\n"); /* We always use this because we need to talk to the PortMapper. */ if (PCedarUDP) f←print(fout, " SunRPCNumbers,\n"); if (PCedarTCP) f←print(fout, " SunRPCStream,\n"); if (PCedar) { f←print(fout, " SunPMap,\n"); f←print(fout, " SunPMapClient,\n"); } f←print(fout, " SunRPCAuth,\n"); if (Imports != NULL) { for (l = Imports; l != NULL; l = l->next) f←print(fout, " %s,\n", l->val); } if (GetPutImports != NULL) { for (l = GetPutImports; l != NULL; l = l->next) f←print(fout, " %s,\n", l->val); } if (TypeDefsExist) f←print(fout, " %sGetPut,\n", svcName); f←print(fout, " %s;\n\n", svcName); f←print(fout, "%s: CEDAR PROGRAM\n", outfile); if (PCedarUDP) f←print(fout, " IMPORTS Basics, SunPMapClient, %s, SunRPCNumbers, SunRPCAuth", SunRPC); else if (PCedarTCP) f←print(fout, " IMPORTS Basics, Convert, Rope, SunPMapClient, SunRPC, %s, SunRPCAuth", SunRPC); else f←print(fout, " IMPORTS %s", SunRPC); if (GetPutImports != NULL) { for (l = GetPutImports; l != NULL; l = l->next) f←print(fout, ", %s", l->val); } if (TypeDefsExist) f←print(fout, ", %sGetPut\n", svcName); else f←print(fout, "\n"); f←print(fout, " EXPORTS %s =\n", svcName); f←print(fout, "BEGIN\n"); f←print(fout, "ROPE: TYPE = Rope.ROPE;\n\n"); f←print(fout, "defaultTimeout: CARDINAL ← 2000;\n"); f←print(fout, "defaultRetries: CARDINAL ← 5;\n\n"); while (def = get←definition()) { foundprogram |= (def->def←kind == DEF←PROGRAM); } if (!foundprogram) { (void) unlink(outfilename); return; } write←stubs(svcName); f←print(fout, "END.\n"); fclose(fout); } /* * Construct a list of IMPORT statements that must go into each output file. * Also construct a hash table of symbol definitions encountered. */ void MakeSymbolTables(name) char *name; { definition *def; StringHashElement elem; case←list *c; decl←list *d; Imports = NULL; GetPutImports = NULL; ImportNames = StringHashCreate(128, NULL, NULL); if (ImportNames == NULL) { fprintf(stderr, "ERROR: couldn't create hash table for import names.\n"); exit(-1); } SymbolNames = StringHashCreate(128, NULL, NULL); if (SymbolNames == NULL) { fprintf(stderr, "ERROR: couldn't create hash table for struct names.\n"); exit(-1); } SeqTypes = StringHashCreate(128, NULL, NULL); if (SeqTypes == NULL) { fprintf(stderr, "ERROR: couldn't create hash table for sequence type names.\n"); exit(-1); } EnumTypes = StringHashCreate(128, NULL, NULL); if (EnumTypes == NULL) { fprintf(stderr, "ERROR: couldn't create hash table for enum type names.\n"); exit(-1); } OutFileType = noFile; CurrentContext->printDirectives = FALSE; open←input(name, "-DRPC←HDR"); /* Just run through the file so that all IMPORT directives get seen by the scanner. */ TypeDefsExist = FALSE; ProgramsExist = FALSE; while (def = get←definition()) { elem = StringHashFind(SymbolNames, def->def←name, insert, NULL); elem->userData = (char *) def; switch (def->def←kind) { case DEF←STRUCT: case DEF←UNION: case DEF←TYPEDEF: case DEF←ENUM: TypeDefsExist = TRUE; break; case DEF←PROGRAM: ProgramsExist = TRUE; break; default: break; } } reinitialize(); } /* * Process IMPORT statements for imported .x files. */ void ProcessImport(name) char *name; { Context *save = CurrentContext; Context import; char infile[256]; definition *def; char buf[512]; list *l; StringHashElement elem; int inserted; ImportModuleRec *m; int foundDefnsFlag = FALSE; int foundTypeDefsFlag = FALSE; if (OutFileType != noFile) return; sprintf(infile, "%s.x", name); SeqTypeId = 0; CurrentContext = &import; reinitialize(); CurrentContext->printDirectives = FALSE; open←input(infile, ""); name = strdup(name); while (def = get←definition()) { elem = StringHashFind(ImportNames, def->def←name, insert, &inserted); elem->userData = (char *) malloc(sizeof(ImportModuleRec)); m = (ImportModuleRec *) (elem->userData); m->moduleName = name; sprintf(buf, "%s.%s", name, StripPrefix(name, def->def←name)); m->defnName = strdup(buf); elem = StringHashFind(SymbolNames, def->def←name, insert, NULL); elem->userData = (char *) def; switch (def->def←kind) { case DEF←STRUCT: case DEF←UNION: case DEF←TYPEDEF: case DEF←ENUM: foundDefnsFlag = TRUE; foundTypeDefsFlag = TRUE; break; case DEF←CONST: foundDefnsFlag = TRUE; break; default: break; } CheckForSeqTypes(def, name); } if (foundDefnsFlag) { l = (list *) malloc(sizeof(list)); l->val = name; l->next = Imports; Imports = l; } if (foundTypeDefsFlag) { l = (list *) malloc(sizeof(list)); sprintf(buf, "%sGetPut", name); l->val = strdup(buf); l->next = GetPutImports; GetPutImports = l; } CurrentContext = save; } /* * Add any SeqType data definitions to the SeqTypes table. */ CheckForSeqTypes(def, importName) definition *def; char *importName; { switch (def->def←kind) { case DEF←STRUCT: CheckStructDef(def, importName); break; case DEF←UNION: CheckUnionDef(def, importName); break; case DEF←TYPEDEF: CheckTypeDef(def, importName); break; default: break; } } CheckStructDef(def, importName) definition *def; char *importName; { decl←list *l; char *name = def->def←name; for (l = def->def.st.decls; l != NULL; l = l->next) { CheckDeclaration(name, &l->decl, importName); } } CheckUnionDef(def, importName) definition *def; char *importName; { case←list *l; char *name = def->def←name; for (l = def->def.un.cases; l != NULL; l = l->next) { if (!streq(l->case←decl.type, "void")) { CheckDeclaration(name, &l->case←decl, importName); } } } CheckTypeDef(def, importName) definition *def; char *importName; { char *name = def->def←name; char *old = def->def.ty.old←type; relation rel = def->def.ty.rel; char buf[256]; int opaqueFlag = FALSE; StringHashElement elem; int inserted; if (!streq(name, old)) { if (streq(old, "string")) { rel = REL←ALIAS; } else if (streq(old, "opaque")) { opaqueFlag = TRUE; } if (rel == REL←ARRAY) { if (!opaqueFlag) { elem = StringHashFind(SeqTypes, def->def.ty.old←type, insert, &inserted); if (inserted) { sprintf(buf, "%s.SeqType%d", importName, SeqTypeId); SeqTypeId++; elem->userData = strdup(buf); } } } } } CheckDeclaration(name, dec, importName) char *name; declaration *dec; char *importName; { StringHashElement elem; int inserted; char buf[256]; if (streq(dec->type, "void")) { return; } if (!streq(dec->type, "string")) { if (dec->rel == REL←ARRAY) { if (!streq(dec->type, "opaque")) { sprintf(buf, "%s.%s", name, dec->name); elem = StringHashFind(SeqTypes, buf, insert, &inserted); if (inserted) { sprintf(buf, "%s.SeqType%d", importName, SeqTypeId); SeqTypeId++; elem->userData = strdup(buf); } } } } } /* * Parse command line arguments */ static parseargs(argc, argv, cmd) int argc; char *argv[]; struct commandline *cmd; { int i; int j; char c; char flag[(1 << 8 * sizeof(char))]; int nflags; cmdname = argv[0]; cmd->infile = cmd->outfile = NULL; if (argc < 2) { return (0); } flag['c'] = 0; flag['h'] = 0; flag['l'] = 0; flag['s'] = 0; for (i = 1; i < argc; i++) { if (argv[i][0] != '-') { if (cmd->infile) { return (0); } cmd->infile = argv[i]; } else { for (j = 1; argv[i][j] != 0; j++) { c = argv[i][j]; switch (c) { case 'c': case 'h': case 'l': if (flag[c]) { return (0); } flag[c] = 1; break; case 'P': if (strcmp(argv[i], "-PCedar") == 0) { PCedar = 1; PCedarUDP = 1; } else if (strcmp(argv[i], "-PCedarStream") == 0) { PCedar = 1; PCedarTCP = 1; } else { return (0); } if (PCedarUDP && PCedarTCP) { fprintf(stderr, "can't specify both PCedar and PCedarStream at the same time\n"); exit(-1); } goto nextarg; case 'o': if (argv[i][j - 1] != '-' || argv[i][j + 1] != 0) { return (0); } if (++i == argc) { return (0); } if (cmd->outfile) { return (0); } cmd->outfile = argv[i]; goto nextarg; case 'p': PFlag = TRUE; break; case 'e': ExpandProcArgs = TRUE; break; case 'r': RecoveryFlag = TRUE; break; default: return (0); } } nextarg: ; } } cmd->cflag = flag['c']; cmd->hflag = flag['h']; cmd->sflag = flag['s']; cmd->lflag = flag['l']; nflags = cmd->cflag + cmd->hflag + cmd->sflag + cmd->lflag; if (nflags == 0) { if (cmd->outfile != NULL || cmd->infile == NULL) { return (0); } } else if (nflags > 1) { return (0); } return (1); }