#include #include /*)BUILD */ #ifdef DOCUMENTATION title untar Read tar (tape archive) format tapes index Read tar (tape archive) format tapes Synopsis untar [- options] [-d directory] [tar_file] Description untar extracts the contents of a Unix tar format tape, simplifing transfer of files from Unix to VMS or other similar operating systems. (Untar runs on VMS, RSTS/E, RSX, or RT11.) If the tar_file is missing, untar will read "tartap.dat" in your current directory. Options Untar recognizes the following options: .lm +4 .s.i -4;-a##Create all files in "ascii" (readable text) format. .s.i -4;-b##Create all files in "binary" format. .s.i -4;-d##(Requires a file name argument). Scan the tar archive for directories, writing a VMS command file to create the directories. (On-the-fly creation has not been implemented.) .s.i -4;-h##(Requires a string argument). If specified, this string is prepended to all filenames before creation. For example, assume untar was invoked as untar -h "dskz:" A filename "foo.bar" would be created as if it were written "dskz:foo.bar". See also the "-n" option. .s.i -4;-n##Ignore directory path information in the archive filename, writing all files to the current directory. .s.i -4;-q##Query. Inquire whether to create each archive member. .s.i -4;-t##List the contents of the archive. .s.i -4;-v##Verbose -- list each file as it is processed. -v2 lists more information. .s.lm -4 If neither -a nor -b are given, untar decides on the output format by examining the filetype. Files with no filetype or whose filetype is included in a built-in list are created in "binary" mode, others in ascii mode. The -a/-b option is uninteresting on Unix, but important on RSTS/E and similar operating systems. Reading the Archive The following procedure is reommended: .s.lm +4 Read the tape onto a disk file, converting from the tape format (which may contain 10240 byte records) to "fixed-block, 512-byte" records. The Decus C distribution contains two Basic-Plus programs (rdmt.bas for RSTS/E and vrdmt.bas for VMS) that read tar files onto disk. While the procedure is generally straight-forward, different operating systems have different requirements. Use untar to list all directories (-d option) and build an appropriate directory structure. Use untar to extract the files. .lm -4 Assumed Filetypes The following filetypes are assumed to be "binary": tsk olb mlb sys sml ulb obj stb bin lda exe sav o a Also, any file with no filetype will be assumed to be "binary". Operating-system Notes Untar has not been tested on Unix. On RSTS/E and RT11, Untar will not create a file that already exists in your directory. If untar cannot create a file, it prompts for a replacement file name. (If run interactively.) #endif /* * D e f i n i t i o n s a n d G l o b a l s */ #define FALSE 0 #define TRUE 1 #define EOS '\0' /* End of string marker */ #ifdef decus #define OPENMODE "rn" #else #define OPENMODE "r" #endif /* * Specify the operating system. */ #define UNIX 0 #define RSX 1 #define VMS 2 #define RT11 3 #ifdef rsx #define OPSYS RSX #endif #ifdef rt11 #define OPSYS RT11 #endif #ifdef unix #define OPSYS UNIX #endif #ifdef vms #define OPSYS VMS #endif #ifndef OPSYS << error, operating system wasn't defined >> #endif #if OPSYS == UNIX #define IO_SUCCESS 0 #define IO_ERROR 1 #endif #if OPSYS == RSX || OPSYS == RT11 extern int $$rsts #endif #if OPSYS == VMS #include /* System status codes */ #include /* I/O request codes */ #include /* String descriptors */ #define IO_SUCCESS SS$_NORMAL #define IO_ERROR errno /* Use global value */ typedef struct dsc$descriptor_s STRING; /* string descriptor */ /* * The following macro builds a descriptor from an argument string. */ #define descrip(text, p) \ ((p)->dsc$a_pointer = text, \ (p)->dsc$w_length = strlen(text), \ (p)->dsc$b_dtype = DSC$K_DTYPE_T, \ (p)->dsc$b_class = DSC$K_CLASS_S) extern int errno; /* VMS last error */ #endif #ifdef vms /* * Create files in vanilla rms on vms. */ #define CREATE(name, mode) (fdopen(creat(name, 0, "rat=cr", "rfm=var"), mode)) #else #define CREATE(name, mode) (fopen(name, mode)) #endif #define DEF_INPUT "tartap.dat" #define RSIZE 512 /* Probably shouldn't change */ #define FNSIZE 100 /* Filename string size */ /* * This structure defines the header of an archive file. * headers are always aligned on a 512 byte boundary. * All information in the header is in readable ascii. * All numbers are stored in octal. The checksum is * calculated differently on different Unix systems. * The file create date is in Unix "time" format. * * If the last character of the name is '/', the * entry is a directory (there is no other data). * Similar magic (unimplemented here) is needed to * recognize "special files". */ typedef struct header { char name[100]; /* Unix file name */ char protmode[8]; /* Protection bits */ char userid[8]; /* User i.d. */ char nlinks[8]; /* Number of links */ char filesize[11]; /* File size in bytes */ char date[13]; /* File create date */ char checksum[8]; /* Random number */ char linkflag[1]; /* '1' (one) if a link */ char linkpath[100]; /* Link to file */ } HEADER; /* * Global variables */ int debug; /* Set for debug printouts */ int verbose; /* Log file */ int nocreate; /* Don't create files/dir's */ int nopath; int list; /* List tar contents */ int ascii; /* Ascii option (not binary) */ int binary; /* Binary output (not ascii) */ int isbinary; /* Mode of this file */ int query; /* Ask about each file. */ int nooutput; /* TRUE to skip this file */ FILE *infd; /* Tar input file */ FILE *outfd; /* Archive member written here */ FILE *dirfd; /* Directory command file */ char *dirfile = NULL; /* Set (-d file) to do direct. */ char *header = ""; /* Set by -h to do header */ union { /* Tar data record */ HEADER h; /* Tar file header */ char b[RSIZE + 1]; /* Input record buffer */ } record; char arch_name[FNSIZE+1]; /* Filename in tar file */ char filename[FNSIZE+1]; /* Filename to create */ char *archivefilename = DEF_INPUT; /* Tar file to process */ char subdir[FNSIZE+1]; /* For vms [...] hacks */ char work[FNSIZE+1]; /* String scratch area */ char line[257]; /* For user prompts */ char filetype[4]; /* For ascii test */ long protmode; /* For Unix create */ long filesize; /* File size (bytes) */ long blocks; /* File size (blocks) */ int bytes_in_last; /* left over bytes */ long inrecords; /* Read from tar input */ char *types[] = { /* Assumed binary */ "tsk", "olb", "mlb", "sys", "sml", "ulb", "obj", "stb", "bin", "lda", "exe", "sav", "o", "a", NULL }; main(argc, argv) int argc; char *argv[]; /* * Untar main program. */ { register int i; getoptions(argc, argv); for (i = 1; i < argc; i++) { if (argv[i] != NULL) { archivefilename = argv[i]; break; } } /* * Process the file. */ if ((infd = fopen(archivefilename, OPENMODE)) == NULL) { perror(archivefilename); fprintf(stderr, "Can't open tar format input file.\n"); exit(IO_ERROR); } if (dirfile != NULL) { if ((dirfd = CREATE(dirfile, "w")) == NULL) { perror(dirfile); fprintf(stderr, "Can't open directory command output file.\n"); exit(IO_ERROR); } nocreate = TRUE; } if (list) nocreate = TRUE; inrecords = 0L; while (feof(infd) == 0 && ferror(infd) == 0) { if (getheader()) getfile(); } if (ferror(infd)) { perror(archivefilename); exit(IO_ERROR); } } int getheader() /* * Read and parse a tar header. Return TRUE if ok, FALSE to skip. * getheader() sets up lots of global stuff. */ { extern long octal_to_long(); /* * Read the next record -- it is a tar member header. */ if (!readrecord()) return (FALSE); /* * Process the archive name field. */ copystring(arch_name, record.h.name, sizeof (record.h.name)); if (arch_name[0] == EOS) /* "end of tape" */ return (FALSE); if (arch_name[strlen(arch_name) - 1] == '/') { arch_name[strlen(arch_name) - 1] = EOS; /* Drop signal */ if (list || verbose > 1) printf("\"%s\" is a directory\n", arch_name); if (!nocreate) getdirectory(); if (dirfd != NULL) dircomfile(); return (FALSE); } /* * Process the link flag */ if (record.h.linkflag[0] == '1') { #if OPSYS == UNIX /* * Do links someday */ copystring(work, record.h.linkpath, sizeof (record.h.linkpath)); if (list || verbose) { printf("\"%s\" links to \"%s\", ignored.\n", arch_name, work); } #else copystring(work, record.h.linkpath, sizeof (record.h.linkpath)); if (list || verbose) { printf("\"%s\" links to \"%s\", ignored.\n", arch_name, work); } #endif return (FALSE); } /* * Save the protection mode for Unix and * process the file size field. */ copystring(work, record.h.protmode, sizeof (record.h.protmode)); protmode = octal_to_long(work); copystring(work, record.h.filesize, sizeof (record.h.filesize)); if ((filesize = octal_to_long(work)) == 0L) { if (list || verbose) printf("\"%s\" has zero length, skipped.\n", arch_name); return (FALSE); } blocks = filesize / RSIZE; bytes_in_last = filesize % RSIZE; if (list || verbose > 1) { printf("\"%s\", %ld bytes (%ld blocks, %d bytes in last)\n", arch_name, filesize, blocks, bytes_in_last); } /* * All processed. Try to create the file. */ if (nocreate || !open_output()) nooutput = TRUE; else nooutput = FALSE; return (TRUE); } getdirectory() /* * Create a directory entry. */ { #if OPSYS == UNIX int status; /* fork/wait status */ int cstatus; /* Child status */ strcpy(filename, arch_name); filename[strlen(filename) - 1] = EOS; /* * How does a mundane create a directory? * Maybe this will work: */ if ((status = vfork()) != 0) { /* * Parent -- wait for the child. */ if (status = wait(&cstatus)) < 0) perror("make directory: child went away"); else if (cstatus != 0) { fprintf(stderr, "error creating directory \"%s\"\n", filename); } } else { status = execl("/bin/mkdir", "mkdir", filename, NULL); if (status == -1) perror("execl of mkdir"); } #else #if OPSYS == VMS && FALSE /* * Note: this doesn't seem to work. */ int status; /* fork/wait status */ int cstatus; /* Child status */ register char *np; extern char *vms_etext(); strcpy(filename, arch_name); squishfilename(filename); while ((np = strchr(filename, '/')) != NULL) { *np = '.'; if (np[1] == EOS) *np = EOS; } if (filename[0] == EOS) return; sprintf(work, "[.%s]", filename); strcpy(filename, work); /* * How does a mundane create a directory? * Maybe this will work: */ if (verbose > 1) printf("Attempting to create directory \"%s\"\n", filename); if ((status = vfork()) != 0) { /* * Parent -- wait for the child. */ if ((status = wait(&cstatus)) < 0) perror("make directory: child went away"); else if (cstatus != 0) { fprintf(stderr, "error (%d) creating directory \"%s\"\n", cstatus, filename); fprintf(stderr, "%s\n", vms_etext(cstatus)); } } else { if ((status = execl("$create", "/dir", filename, NULL)) == -1) perror("execl of create/dir"); } #else /* * Can't create directories on RSTS/E, RSX, or RT11. */ if (verbose) printf("\"%s\" is a directory, skipped.\n", arch_name); #endif #endif } dircomfile() /* * Create a vax command file to build a directory. */ { register char *np; strcpy(filename, arch_name); squishfilename(filename); while ((np = strchr(filename, '/')) != NULL) *np = '.'; fprintf(dirfd, "$ cre/dir [.%s]\n", filename); } getfile() /* * Process the current archive member */ { register long count; for (count = 1; count <= blocks; count++) { if (readrecord()) output(RSIZE); else { perror(archivefilename); fprintf(stderr, "reading \"%s\"\n", arch_name); filesize = 0L; } } if (bytes_in_last > 0) { if (readrecord()) output(bytes_in_last); else { perror(archivefilename); fprintf(stderr, "reading last block of \"%s\"\n", arch_name); } } if (!nooutput) fclose(outfd); } int open_output() /* * Create the current tar member. Munging it as necessary for the * opeating system. TRUE if ok, FALSE to skip the file. */ { register char *np; register int fileid; if (query) { sprintf(work, "Create \"%s\"", arch_name); if (!getyesno(work, "Yes")) return (FALSE); } strcpy(filename, arch_name); #if OPSYS == UNIX /* * Create the file on Unix. */ if (nopath) { while ((np = strchr(filename, '/')) != NULL) strcpy(filename, np + 1); } sprintf(work, "%s%s", header, filename); /* Add header */ strcpy(filename, work); for (;;) { if (verbose) printf("\"%s\" => \"%s\"\n", arch_name, filename); if (outfd = fdopen(creat(filename, protmode), "w")) != NULL) break; perror(filename); fprintf(stderr, "Can't create \"%s\" (archive is \"%s\")\n", filename, arch_name); if (!isatty(fileno(stdin)) || feof(stdin)) { printf("Skipping archive \"%s\"\n", arch_name); return (FALSE); } fprintf(stderr, "New file, to skip file: "); if (!getcommand() || line[0] == EOS) { return (FALSE); } strcpy(filename, line); protmode = 0666; /* Get default mode */ } #endif #if OPSYS == VMS /* * Create the file on VMS * * First, convert any directory paths to VMS flavor: * "foo/bar/file_name.extension" => "[.foo.bar]filename.ext" */ squishfilename(filename); /* Remove non-filename stuff */ testbinary(filename); /* Check (fix) .ext filetype */ subdir[0] = EOS; /* Build subdirectory strings */ while ((np = strchr(filename, '/')) != NULL) { *np = EOS; sprintf(work, "%s.%s", subdir, filename); strcpy(subdir, work); strcpy(filename, np + 1); } if (!nopath && subdir[0] != EOS) { sprintf(work, "[%s]%s", subdir, filename); strcpy(filename, work); } sprintf(work, "%s%s", header, filename); /* Add header */ strcpy(filename, work); for (;;) { if (verbose) printf("\"%s\" => \"%s\"\n", arch_name, filename); if (isbinary) outfd = fopen(filename, "w"); else { fileid = creat(filename, 0, "rat=cr", "rfm=var"); outfd = (fileid == -1) ? NULL : fdopen(fileid, "w"); } if (outfd != NULL) break; perror(filename); fprintf(stderr, "Can't create \"%s\" (archive is \"%s\")\n", filename, arch_name); if (!isatty(fileno(stdin)) || feof(stdin)) { printf("Skipping archive \"%s\"\n", arch_name); return (FALSE); } fprintf(stderr, "New file, to skip file: "); if (!getcommand() || line[0] == EOS) { return (FALSE); } strcpy(filename, line); } #endif #if OPSYS == RSX || OPSYS == RT11 /* * Create the file on RSTS or RSX. * * Strip out any directory paths. (Note that this * isn't quite correct as paths are legal on P/OS.) */ squishfilename(filename); /* Remove funny text */ while ((np = strchr(filename, '/')) != NULL) strcpy(filename, np + 1); sprintf(work, "%s%s", header, filename); /* Add header */ strcpy(filename, work); for (;;) { /* * RSTS and RT11 don't have file version numbers. * Make sure the file doesn't exist already. */ testbinary(filename); if (verbose) printf("\"%s\" => \"%s\"\n", arch_name, filename); #if OPSYS == RT11 outfd = fopen(filename, "r"); #else if ($$rsts) outfd = fopen(filename, "r"); else outfd = NULL; #endif if (outfd == NULL) outfd = fopen(filename, (isbinary) ? "wn" : "w"); else { fclose(outfd); fprintf(stderr, "File already exists. "); outfd = NULL; } if (outfd != NULL) break; /* File is open correctly */ else { fprintf(stderr, "Can't create \"%s\", (archive is \"%s\").\n", filename, arch_name); if (!isatty(fileno(stdin)) || feof(stdin)) { printf("Skipping archive \"%s\"\n", arch_name); return (FALSE); } fprintf(stderr, "New file, to skip file: "); if (!getcommand() || line[0] == EOS) return (FALSE); strcpy(filename, line); } } #endif return (TRUE); } output(nbytes) int nbytes; /* * Output the first nbytes from the record. */ { if (nooutput) return; if (isbinary) fwrite(record.b, sizeof (char), nbytes, outfd); else { /* * On RSTS/E, RSX, RT11, and VMS, this * outputs "vanilla ascii" records. */ record.b[nbytes] = EOS; fputs(record.b, outfd); } } int readrecord() /* * Read one 512 byte record from the archive. TRUE if ok, FALSE if error. */ { if (fread(record.b, sizeof (char), RSIZE, infd) != RSIZE) return (FALSE); else { inrecords++; return (TRUE); } } testbinary(name) char *name; /* * Set isbinary flag. */ { register char *np; register char **tp; if (ascii) /* Always ascii */ isbinary = FALSE; else if (binary /* Always binary */ || (np = strchr(name, '.')) == NULL /* no dot */ || *++np == EOS) /* no type */ isbinary = TRUE; /* is binary */ else { if (strlen(np) > 3) /* Long filetype? */ np[3] = EOS; /* Trim filetype */ strcpy(filetype, np); /* Get a copy */ for (np = filetype; *np != EOS; np++) { if (isupper(*np)) *np = tolower(*np); } for (tp = types; *tp != NULL; tp++) { if (strcmp(*tp, filetype) == 0) break; /* Found a type */ } if (*tp != NULL) isbinary = TRUE; /* Known type */ else isbinary = FALSE; /* Others are ascii */ } } long octal_to_long(in) register char *in; /* * Convert octal string to a long result. */ { long result; result = 0L; while (*in >= '0' && *in <= '7') { result <<= 3; result += (*in++ - '0'); } return (result); } copystring(out, in, size) register char *out; register char *in; int size; /* * Does Basic-Plush cvt$$(in, 1+2+4) which drops garbage, * blanks, and tabs. */ { char *inend; int c; inend = &in[size]; while (in < inend) { c = *in++ & 0177; /* Eat parity */ if (isprint(c) && c != ' ') *out++ = c; } *out = EOS; } squishfilename(string) char *string; /* * Removes non rad-50 characters (except '/' and all but the last '.') * from the argument. */ { register char *inp; register char *outp; for (inp = outp = string; *inp != EOS; *inp++) { if (isalpha(*inp) || isdigit(*inp) || *inp == '/' || *inp == '.') *outp++ = *inp; } *outp = EOS; /* * Remove all but the last '.' */ if ((inp = strrchr(string, '.')) != NULL) *inp = '~'; /* Remember last '.' */ for (inp = string; (inp = strchr(inp, '.')) != NULL;) strcpy(inp, inp + 1); /* Erase other dots */ if ((inp = strchr(string, '~')) != NULL) *inp = '.'; /* Fix last one again */ } /* * G E T O P T I O N S * * Generalized command line argument processor. The following * types of arguments are parsed: * flags The associated int global is incremented: * -f f-flag set to 1 * -f123 f-flag set to 123 (no separator) * -fg f-flag and g-flag incremented. * values A value must be present. The associated * int global receives the value: * -v123 value set to 123 * -v 123 value set to 123 * arguments The associated global (a char *) is * set to the next argument: * -f foo argument set to "foo" */ #define FLAG 0 #define VALUE 1 #define ARG 2 #define ERROR 3 typedef struct argstruct { char opt; /* Option byte */ char type; /* FLAG/VALUE/ARG */ int *name; /* What to set if option seen */ char *what; /* String for error message */ } ARGSTRUCT; static ARGSTRUCT arginfo[] = { { 'a', FLAG, &ascii, "always ascii output" }, { 'b', FLAG, &binary, "always binary output" }, { 'd', ARG, &dirfile, "directory command" }, { 'h', ARG, &header, "file name header" }, { 'n', FLAG, &nopath, "ignore archive paths" }, { 'q', FLAG, &query, "query each file" }, { 't', FLAG, &list, "list entries only" }, { 'v', FLAG, &verbose, "verbose" }, { EOS, ERROR, NULL, NULL }, }; static char *argtype[] = { "flag", "takes value", "takes argument" }; static getoptions(argc, argv) int argc; char **argv; /* * Process arg's */ { register char *ap; register int c; register ARGSTRUCT *sp; int i; int helpneeded; getredirection(argc, argv); helpneeded = FALSE; for (i = 1; i < argc; i++) { if ((ap = argv[i]) != NULL && *ap == '-') { argv[i] = NULL; for (ap++; (c = *ap++) != EOS;) { c = tolower(c); sp = arginfo; while (sp->opt != EOS && sp->opt != c) sp++; switch (sp->type) { case FLAG: /* Set the flag */ if (!isdigit(*ap)) { ++(*sp->name); break; } case VALUE: /* -x123 */ if (isdigit(*ap)) { *((int **)sp->name) = atoi(ap); *ap = EOS; } else if (*ap == EOS && ++i < argc) { *((int **)sp->name) = atoi(argv[i]); argv[i] = NULL; } else { fprintf(stderr, "Bad option '%c%s' (%s)", c, ap, sp->what); fprintf(stderr, ", ignored\n"); helpneeded++; } break; case ARG: /* -x foo */ if (++i < argc) { *((char **) sp->name) = argv[i]; argv[i] = NULL; } else { fprintf(stderr, "Argument needed for '%c' (%s)", c, sp->what); fprintf(stderr, ", ignored\n"); helpneeded++; } break; case ERROR: fprintf(stderr, "Unknown option '%c', ignored\n", c); helpneeded++; break; } } } } if (helpneeded > 0) { for (sp = arginfo; sp->opt != EOS; sp++) { fprintf(stderr, "'%c' -- %s (%s)\n", sp->opt, sp->what, argtype[sp->type]); } } } int getyesno(prompt, normal) char *prompt; /* Prompt string */ char *normal; /* Default answer "Yes" or "No" */ /* * Asks a yes/no question. */ { register char *lp; for (;;) { fprintf(stderr, "%s? (Yes/No) <%s>: ", prompt, normal); if (!getcommand()) return(FALSE); /* End of file is very false */ if (line[0] == EOS) strcpy(line, normal); for (lp = line; *lp != EOS; lp++) { if (isupper(*lp)) *lp = tolower(*lp); } if (strcmp(line, "yes") == 0 || strcmp(line, "ye") == 0 || strcmp(line, "y") == 0) return (TRUE); else if (strcmp(line, "no") == 0 || strcmp(line, "n") == 0) return (FALSE); fprintf(stderr, "Please answer 'yes' or 'no'.\n"); } } int getcommand() /* * Read text from keyboard to global line[]. * Return FALSE on end of file. Note: rt11 probably trashes the * job so you'll never see the end of file. */ { register char *lp; register char c; lp = line; while ((c = getchar()) != EOF) { if (c == EOS || c == '\r') /* Skip CR or NULL */ continue; /* Rt11 last block */ if (c == '\n') { /* * Squeeze out trailing blanks */ while (lp > line && lp[-1] == ' ') lp--; *lp = 0; return(TRUE); } *lp++ = c; } line[0] = EOS; return(FALSE); } /* * getredirection() is intended to aid in porting C programs * to VMS (Vax-11 C) which does not support '>' and '<' * I/O redirection. With suitable modification, it may * useful for other portability problems as well. */ #include getredirection(argc, argv) int argc; char **argv; /* * Process vms redirection arg's. Exit if any error is seen. * If getredirection() processes an argument, argv[i], it is changed * to NULL. * * Warning: do not try to simplify the code for vms. The code * presupposes that getredirection() is called before any data is * read from stdin or written to stdout. * * Normal usage is as follows: * * main(argc, argv) * int argc; * char *argv[]; * { * register int i; * int nargs; * * getredirection(argc, argv); ** setup redirection * for (nargs = 0, i = 1; i < argc, i++) { * if (argv[i] == NULL) ** skip if processed * continue; ** by getredirection() * nargs++; ** here is an argument * ... ** process argv[i] * } * if (nargs == 0) { ** no arguments given * ... * } * } */ { #ifdef vms register char *ap; /* Argument pointer */ int i; /* argv[] index */ int file; /* File_descriptor */ extern int errno; /* Last vms i/o error */ for (i = 1; i < argc; i++) { /* Do all arguments */ if (*(ap = argv[i]) == '<') { /* ') { /* >file or >>file */ if (*ap == '>') { /* >>file */ /* * If the file exists, and is writable by us, * call freopen to append to the file (using the * file's current attributes). Otherwise, create * a new file with "vanilla" attributes as if * the argument was given as ">filename". * access(name, 2) is TRUE if we can write on * the specified file. */ if (access(++ap, 2) == 0) { if (freopen(ap, "a", stdout) == NULL) { perror(ap); exit(errno); } else goto erase_arg; } /* If file accessable */ else ; /* Else it's just >file */ } /* * On vms, we want to create the file using "standard" * record attributes. create(...) creates the file * using the caller's default protection mask and * "variable length, implied carriage return" * attributes. dup2() associates the file with stdout. */ if ((file = creat(ap, 0, "rat=cr", "rfm=var")) == -1 || dup2(file, fileno(stdout)) == -1) { perror(ap); /* Can't create file */ exit(errno); /* is a fatal error */ } /* If '>' creation */ erase_arg: argv[i] = NULL; /* red. erases argument */ } /* If redirection */ } /* For all arguments */ #endif } #ifdef vms /* * VMS error formatter */ static char errname[257]; /* Error text stored here */ static $DESCRIPTOR(err, errname); /* descriptor for error text */ char * vms_etext(errorcode) int errorcode; { char *bp; short errlen; /* Actual text length */ lib$sys_getmsg(&errorcode, &errlen, &err, &15); /* * Trim trailing junk. */ for (bp = &errname[errlen]; --bp >= errname;) { if (isgraph(*bp) && *bp != ' ') break; } bp[1] = EOS; return(errname); } #endif