/* ELLE - Copyright 1982, 1984, 1987 by Ken Harrenstien, SRI International * This software is quasi-public; it may be used freely with * like software, but may NOT be sold or made part of licensed * products without permission of the author. */ /* * EEFILE File reading/writing functions */ #include "elle.h" #include /* Use "standard" I/O package for writing */ #ifndef BUFSIZ #define BUFSIZ BUFSIZE /* Some places use wrong name in stdio.h */ #endif /*-BUFSIZ*/ #if V6 struct stat { int st_dev; int st_ino; char *st_mode; char st_nlink; char st_uid; char st_gid; char st_size0; char st_size; int st_addr[8]; long st_atime; long st_mtime; }; #define ENOENT (2) /* Syscall error - no such file or dir */ #else #include #include #include #endif /*-V6*/ #if TOPS20 #include /* Get open mode bits */ #endif extern char *strerror(); /* Return error string for errno */ extern struct buffer *make_buf(), *find_buf(); char *fncons(), *last_fname(); int hoardfd = -1; /* Retain a FD here to ensure we can always write */ /* Flags for iwritfile() */ #define WF_SMASK 07 /* Source Mask */ #define WF_SBUFF 0 /* source: Buffer */ #define WF_SREG 1 /* source: Region */ #define WF_SKILL 2 /* source: Last Kill */ #define WF_ASK 010 /* Ask for filename to write to */ static int iwritfile(); /* EFUN: "Find File" */ /* Ask user for a filename and do a find_file for it. * If buffer exists for that filename, select that buffer. * Else create a buffer for it, and read in the file if it exists. */ f_ffile() { int find_file(); #if IMAGEN hack_file("Visit file: ", find_file); #else hack_file("Find file: ", find_file); #endif /*-IMAGEN*/ } /* EFUN: "Read File" */ /* User read_file function, asks user for a filename and reads it */ f_rfile() { u_r_file("Read file: "); } /* EFUN: "Visit File" */ /* Same as Read File, with different prompt. */ f_vfile() { u_r_file("Visit file: "); } u_r_file(prompt) char *prompt; { register char *f_name; register struct buffer *b; if((f_name = ask (prompt))==0) /* prompt user for filename */ return; /* user punted... */ b = cur_buf; if(*f_name == '\0') { if (b -> b_fn == 0) ding("No default file name."); else read_file(b -> b_fn); } else read_file(f_name); chkfree(f_name); } /* EFUN: "Insert File" */ /* Asks for a filename and inserts the file at current location. * Point is left at beginning, and the mark at the end. */ f_ifile() { int ins_file(); hack_file("Insert file: ", ins_file); } /* EFUN: "Save File" */ /* Save current buffer to its default file name */ f_sfile() { if(cur_buf->b_flags&B_MODIFIED) return(iwritfile(WF_SBUFF)); /* Write buffer, don't ask */ else { saynow("(No changes need to be written)"); return(1); } } #if FX_SAVEFILES || FX_WFEXIT /* EFUN: "Save All Files" */ /* F_SAVEFILES - Offer to save all modified files. * With argument, doesn't ask. * Returns 0 if user aborts or if an error happened. */ f_savefiles() { register struct buffer *b, *savb; register int res = 1; char *ans; savb = cur_buf; for (b = buf_head; res && b; b = b->b_next) if ((b->b_flags & B_MODIFIED) && b->b_fn) { if(exp_p) /* If arg, */ { chg_buf(b); /* just save, don't ask */ res = f_sfile(); continue; /* Check next buffer */ } /* Ask user whether to save */ ans = ask("Buffer %s contains changes - write out? ", b->b_name); if(ans == 0) { res = 0; /* User aborted */ break; } if (upcase(*ans) == 'Y') { chg_buf(b); res = f_sfile(); /* Save File */ } chkfree(ans); } chg_buf(savb); return(res); } #endif /*FX_SAVEFILES||FX_WFEXIT*/ /* EFUN: "Write File" */ /* Write out the buffer to an output file. */ f_wfile() { return iwritfile(WF_ASK|WF_SBUFF); } /* EFUN: "Write Region" */ /* Write region out to a file */ f_wreg() { return iwritfile(WF_ASK|WF_SREG); /* Ask, write region */ } #if FX_WLASTKILL /* EFUN: "Write Last Kill" (not EMACS) */ /* Write current kill buffer out to a file. ** This is mainly for MINIX. */ extern int kill_ptr; /* From EEF3 */ extern SBSTR *kill_ring[]; f_wlastkill() { return iwritfile(WF_ASK|WF_SKILL); } #endif /* HACK_FILE - intermediate subroutine */ hack_file(prompt, rtn) char *prompt; int (*rtn)(); { register char *f_name; if((f_name = ask(prompt)) == 0) return; if (*f_name != '\0') /* Check for null answer */ (*rtn)(f_name); chkfree(f_name); } /* FIND_FILE(f_name) * If there is a buffer whose fn == f_name, select that buffer. * Else create one with name of the last section of f_name and * read the file into that buffer. */ find_file(f_name) register char *f_name; { register struct buffer *b; register char *ans; char *lastn; int fd; #if IMAGEN char real_name[128]; /* File name w/ expanded ~ and $ */ expand_file(real_name, f_name); f_name = real_name; #endif /*IMAGEN*/ for (b = buf_head; b; b = b -> b_next) if(b->b_fn && (strcmp (b -> b_fn, f_name) == 0)) break; if (b) /* if we found one */ { sel_buf(b); /* go there */ return; /* and we are done */ } if((fd = open(f_name,0)) < 0) /* See if file exists */ { if(errno != ENOENT) /* No, check reason */ { ferr_ropn(); /* Strange error, complain */ return; /* and do nothing else. */ } } else close(fd); /* Found! Close FD, since the */ /* read_file rtn will re-open. */ lastn = last_fname(f_name); /* Find buffer name */ b = find_buf(lastn); /* Is there a buffer of that name? */ if (b && (ex_blen(b) || b->b_fn)) { ans = ask("Buffer %s contains %s, which buffer shall I use? ", b -> b_name, b->b_fn ? b->b_fn : "something"); if(ans == 0) return; /* Aborted */ if (*ans != '\0') /* if null answer, use b */ b = make_buf(ans); /* else use ans */ chkfree(ans); } else b = make_buf(lastn); sel_buf(b); if(fd < 0) /* If file doesn't exist, */ { set_fn(f_name); /* just say "new" and set filename */ return; /* and return right away. */ } if (read_file(f_name)==0) /* File exists, read it in! */ { if(b->b_fn) /* Failed... if filename, */ { chkfree(b->b_fn); /* flush the filename. */ b->b_fn = 0; } } } /* READ_FILE(f_name) * Reads file into current buffer, flushing any * previous contents (if buffer modified, will ask about saving) * Returns 0 if failed. */ read_file(f_name) char *f_name; { #if IMAGEN struct stat s; char real_name[128]; /* File name w/ expanded ~ and $ */ #endif /*IMAGEN*/ if(!zap_buffer()) /* Flush the whole buffer */ return; /* Unless user aborts */ #if IMAGEN expand_file(real_name, f_name); f_name = real_name; /* Hack, hack! */ #endif /*IMAGEN*/ set_fn(f_name); if (ins_file(f_name)==0) return 0; f_bufnotmod(); /* Say not modified now */ #if IMAGEN stat(f_name, &s); /* Get file stat */ cur_buf->b_mtime = s.st_mtime; /* and pick out last-modified time */ #endif /*IMAGEN*/ return 1; } /* INS_FILE(f_name) * Inserts file named f_name into current buffer at current point * Point is not moved; mark is set to end of inserted stuff. * Returns 0 if failed, 1 if won. */ ins_file (f_name) char *f_name; { register int ifd; register SBSTR *sd; chroff insdot; /* To check for range of mods */ #if IMAGEN char real_name[128]; /* File name w/ expanded ~ and $ */ expand_file(real_name, f_name); f_name = real_name; #endif /*IMAGEN*/ #if !(TOPS20) if((ifd = open(f_name,0)) < 0) #else if((ifd = open(f_name,O_RDONLY|O_UNCONVERTED)) < 0) #endif /*TOPS20*/ { ferr_ropn(); /* Can't open, complain */ return 0; /* no redisplay */ } errno = 0; if((sd = sb_fduse(ifd)) == 0) { if (ifd >= SB_NFILES) dingtoo(" Cannot read - too many internal files"); else if (errno) ferr_ropn(); else errbarf("SB rtn cannot read file?"); close(ifd); return 0; } sb_sins(cur_buf,sd); insdot = e_dot(); f_setmark(); /* Set mark at current ptr */ if(cur_dot != insdot) /* If pointer was advanced, */ buf_tmat(insdot); /* then stuff was inserted */ e_gocur(); return 1; } ferr_ropn() { ferr(" Cannot read"); } ferr_wopn() { ferr(" Cannot write"); } ferr(str) char *str; { saytoo(str); saytoo(" - "); dingtoo(strerror(errno)); } /* IWRITFILE - auxiliary for writing files. ** Returns 1 if write successful, 0 if not. */ static int iwritfile(flags) int flags; { register struct buffer *b; register char *o_name; /* output file name */ int styp = flags & WF_SMASK; /* Source type, one of WF_Sxxx */ char *prompt; #ifdef STDWRITE register FILE *o_file; /* output file pointer */ char obuf[BUFSIZ]; chroff dotcnt; #endif /*STDWRITE*/ int ofd; /* output file FD */ SBSTR *sd; char fname[FNAMSIZ]; /* To avoid chkfree hassle */ char newname[FNAMSIZ]; /* for robustness */ char oldname[FNAMSIZ]; /* ditto */ int res; struct stat statb; int statres; #if IMAGEN struct stat s; char real_name[128]; /* File name w/ expanded ~ and $ */ #endif /*IMAGEN*/ res = 1; /* Let's keep track of success */ /* Check for existence of source, and set prompt string */ switch(styp) { case WF_SBUFF: prompt = "Write File: "; break; case WF_SREG: if(!mark_p) { dingtoo(" No Mark!"); return(0); } prompt = "Write Region: "; break; #if FX_WLASTKILL case WF_SKILL: if(!kill_ring[kill_ptr]) { dingtoo("No killed stuff"); return(0); } prompt = "Write Last Kill: "; break; #endif default: /* Internal error */ errbarf("bad iwritfile arg"); return 0; } if (flags&WF_ASK) { if((o_name = ask(prompt))==0) return(0); /* User punted. */ strcpy(&fname[0], o_name); /* Copy filename onto stack */ chkfree(o_name); } o_name = &fname[0]; b = cur_buf; if (!(flags&WF_ASK) || (*o_name == '\0')) { if (b->b_fn == 0) { ding("No default file name."); return(0); } strcpy(o_name, b->b_fn); } #if IMAGEN expand_file(real_name, o_name); o_name = real_name; /* Hack, hack */ #endif /*IMAGEN*/ statres = stat(o_name,&statb); /* Get old file's info (if any) */ #if IMAGEN /* Now, make sure someone hasn't written the file behind our backs */ if ((styp==WF_SBUFF) && !(flags&WF_ASK) && b->b_fn && stat(b->b_fn, &s) >= 0) if (s.st_mtime != b->b_mtime) { char *ans; ans = ask("Since you last read \"%s\", someone has changed it.\nDo you want to write it anyway (NOT RECOMMENDED!)? ", b->b_fn); if (ans == 0 || upcase(*ans) != 'Y') { ding("I suggest you either read it again, or\nwrite it to a temporary file, and merge the two versions manually."); if (ans) chkfree(ans); return(0); } if (ans) chkfree(ans); } #endif /*IMAGEN*/ /* Try to get around major UNIX screw of smashing files. * This still isn't perfect (screws up with long filenames) but... * 1. Write out to * 2. Rename to (may have to delete existing ) * 3. Rename to . */ fncons(oldname,ev_fno1,o_name,ev_fno2); /* Set up "old" filename */ fncons(newname,ev_fnn1,o_name,ev_fnn2); /* Set up "new" filename */ unlink(newname); /* Ensure we don't clobber */ unhoard(); /* Now give up saved FD */ #if !(V6) /* Standard V6 doesn't have access call */ if(statres >= 0) /* If file exists, */ { if(access(o_name, 2) != 0) /* check for write access */ { ferr_wopn(); res = 0; /* Failure */ goto wdone; } } #endif /*-V6*/ #ifdef STDWRITE if(flags&WF_ASK) { if((o_file = fopen(newname, "w")) ==0) /* Create new output file */ { ferr_wopn(); res = 0; /* Failure */ goto wdone; } setbuf(o_file,obuf); /* Ensure always have buffer */ } else /* New stuff */ #endif /*STDWRITE*/ { #if !(TOPS20) if((ofd = creat(newname,ev_filmod)) < 0) #else if((ofd = open(newname,O_WRONLY|O_UNCONVERTED)) < 0) #endif /*TOPS20*/ { ferr_wopn(); res = 0; /* Failure */ goto wdone; } } if (styp==WF_SBUFF) set_fn(o_name); /* Won, so set default fn for buff */ #if IMAGEN saynow("Writing "); switch(styp) { case WF_SBUFF: saytoo(b->b_fn); break; case WF_SREG: saytoo("region"); break; #if FX_WLASTKILL case WF_SKILL: saytoo("last kill"); break; #endif } sayntoo("..."); #else saynow("Writing..."); #endif /*-IMAGEN*/ #if !(TOPS20) /* T20 does all this already */ if(statres >= 0) /* Get old file's modes */ { /* Try to duplicate them */ /* Do chmod first since after changing owner we may not ** have permission to change mode, at least on V6. */ chmod(newname,statb.st_mode & 07777); #if V6 chown(newname, (statb.st_gid<<8)|(statb.st_uid&0377)); #else chown(newname,statb.st_uid,statb.st_gid); #endif /*-V6*/ } #if V6 /* If no old file existed, and we are a V6 system, try to set * the modes explicitly. On V7 we're OK because the user can * diddle "umask" to get whatever is desired. * On TOPS-20 of course everything is all peachy. */ else chmod(newname, ev_filmod); #endif /*V6*/ #endif /*TOPS20*/ #ifdef STDWRITE if(flags&WF_ASK) { switch(styp) { case WF_SBUFF: dotcnt = e_blen(); e_gobob(); break; case WF_SREG: if((dotcnt = mark_dot - cur_dot) < 0) { e_goff(dotcnt); dotcnt = -dotcnt; } else e_gocur(); break; /* WF_SKILL not implemented here */ } while(--dotcnt >= 0) putc(sb_getc(((SBBUF *)b)), o_file); e_gocur(); fflush(o_file); /* Force everything out */ res = ferror(o_file); /* Save result of stuff */ fclose(o_file); /* Now flush FD */ } else /* New stuff */ #endif /*STDWRITE*/ { switch(styp) { case WF_SBUFF: res = sb_fsave((SBBUF *)b, ofd); break; case WF_SREG: e_gocur(); sd = e_copyn((chroff)(mark_dot - cur_dot)); res = sbx_aout(sd, 2, ofd); sbs_del(sd); break; #if FX_WLASTKILL case WF_SKILL: res = sbx_aout(kill_ring[kill_ptr], 2, ofd); break; #endif } close(ofd); } if(errno = res) { ferr(" Output error"); res = 0; /* Failure */ goto wdone; } else res = 1; /* Success so far */ if(styp == WF_SBUFF) f_bufnotmod(); /* Reset "buffer modified" flag */ /* Here we effect the screw-prevention steps explained earlier. */ /* TOPS-20, with generation numbers, need not worry about this. */ #if TOPS20 saynow("Written"); #else /*-TOPS20*/ #if IMAGEN /* KLH -- This conditional bracketting is prone to lossage */ /* Only create the .BAK file once per editing session!! */ if ((styp==WF_SBUFF) || !(b->b_flags & B_BACKEDUP)) { if (styp==WF_SBUFF) b->b_flags |= B_BACKEDUP; #endif /*IMAGEN*/ unlink(oldname); /* remove any existing "old" file */ if(link(o_name,oldname) == 0) /* Rename current to "old" */ unlink(o_name); /* Here is the critical point... if we stop here, there is no * longer any file with the appropriate filename!!! */ #if IMAGEN } else unlink(o_name); #endif /*IMAGEN*/ if(link(newname,o_name) == 0) /* Rename "new" to current */ { unlink(newname); #if IMAGEN sayntoo("OK"); #else saynow("Written"); #endif /*-IMAGEN*/ } else { dingtoo("rename error!"); res = 0; } #endif /*-TOPS20*/ #if IMAGEN /* Update the last-modified time for the file in this buffer */ if ((styp == WF_SBUFF) && b->b_fn) { stat(b->b_fn, &s); b->b_mtime = s.st_mtime; } #endif /*IMAGEN*/ wdone: hoard(); /* Get back a retained FD */ return(res); } /* FNCONS(dest,pre,f_name,post) * Specialized routine to cons up a filename string into "dest", * given prefix and postfix strings to be added onto last component of * filename. */ char * fncons(dest, pre, f_name, post) char *dest,*pre,*f_name,*post; { register char *cp, *cp2; char *last_fname(); cp = dest; *cp = 0; /* Make dest string null initially */ cp2 = last_fname(f_name); /* Get pointer to beg of last name */ strncat(cp,f_name,cp2-f_name); /* Copy first part of filename */ if(pre) strcat(cp, pre); /* If prefix exists, add it on */ cp = last_fname(cp); /* Recheck in case levels added */ strcat(cp, cp2); /* Now add last name */ if(cp2 = post) /* If there's a postfix, must check */ { cp[FNAMELEN-strlen(cp2)] = 0; /* and cut dest so postfix */ strcat(cp, cp2); /* will fit on end. */ } return(dest); } /* LAST_FNAME(string) * Get the last component of a file name. Returns pointer to * start of component; does NOT copy string! */ char * last_fname(f_name) char *f_name; { register char *cp, *p; register int c; p = f_name; /* pointer to last slash */ cp = p; while(c = *cp++) if(c == '/') p = cp; /* point to after the slash */ return(p); } /* SET_FN(string) * Set the default filename for current buffer to "string". */ set_fn (string) char *string; { register struct buffer *b; register char *str; #if IMAGEN register char *cp; register int len; #endif /*IMAGEN*/ char *strdup(); b = cur_buf; str = strdup(string); /* Copy now in case copying self */ if(b->b_fn) chkfree(b->b_fn); b -> b_fn = str; #if IMAGEN /* Do mode determination based on file name (HACK HACK) */ len = strlen(str); b->b_flags &= ~(B_CMODE|B_TEXTMODE); if (len > 4) { if (strcmp(&str[len - 5], "draft") == 0) b->b_flags |= B_TEXTMODE; else { cp = &str[len - 4]; if (strcmp(cp, ".txt") == 0 || strcmp(cp, ".mss") == 0) b->b_flags |= B_TEXTMODE; } } if (len > 2) { cp = &str[len - 2]; if (strcmp(cp, ".h") == 0 || strcmp(cp, ".c") == 0) b->b_flags |= B_CMODE; } #endif /*IMAGEN*/ redp(RD_MODE); } /* SAVEWORLD - Attempt to save all changes user has made. * Currently this amounts to writing out all modified buffers * to the files $HOME/+buffername. If a buffer is given as argument, * only that buffer is saved. * This is only called from the error handling routines with * the TTY either gone or in normal (non-edit) mode. The "grunt" * flag says whether to output feedback during the saving process. */ saveworld(bp, grunt) struct buffer *bp; int grunt; { register struct buffer *b; register int wfd; char sfname[FNAMSIZ]; struct buffer *sel_mbuf(); unhoard(); /* Ensure a FD is free for writing */ if(b = bp) goto once; while(!bp && (b = sel_mbuf(b))) { once: strcat(strcat(strcpy(sfname,homedir),"/+"),b->b_name); if(grunt) printf("Saving %s...",sfname); #if !(TOPS20) if((wfd = creat(sfname, ev_filmod)) < 0) #else if((wfd = open(sfname,O_WRONLY|O_UNCONVERTED)) < 0) #endif /*TOPS20*/ { if(grunt) printf(" error - %s\n", strerror(errno)); } else { sb_fsave((SBBUF *)b, wfd); close(wfd); if(grunt) printf("\n"); } b->b_flags &= ~B_MODIFIED; } hoard(); } /* HOARD, UNHOARD - Routines to save a FD for writing, to make sure * that we can always write out a buffer no matter how many * file descriptors we are currently using. */ hoard() /* Stash away a FD */ { if(hoardfd <= 0) #if !(TOPS20) hoardfd = open("nul:", 1); #else hoardfd = open("/dev/null", 1); #endif } unhoard() /* Give up our stashed FD so it can be re-used */ { close(hoardfd); hoardfd = -1; } #if IMAGEN #include #include /* * expand_file: expand any ~user-name/ or $env-var/ prefixes in sfn, * producing the full name in dfn */ expand_file(dfn, sfn) register char *dfn, *sfn; { register char *sp, *tp; register int c; register struct passwd *pw; char ts[128]; /* HORRIBLE, GROSS, DISGUSTING HACK: if the destination and * source strings are identical (same pointer), then do not * do any expansion--this happens to work with the current * structure very well, since multiple expansions may happen. */ if (dfn == sfn) return; ts[0] = 0; /* If have a leading $, then expand environment variable */ if (*sfn == '$') { ++sfn; tp = ts; while (*tp++ = *sfn) if (!isalnum(*sfn)) break; else ++sfn; *--tp = 0; /* Just in case */ strcpy(ts, getenv(ts)); /* MARGINAL!! */ } /* If have leading ~, then expand login name (null means $HOME) */ else if (*sfn == '~') { ++sfn; if (*sfn == '/' || *sfn == 0) strcpy(ts, getenv("HOME")); else { tp = ts; while (*sfn && *sfn != '/') *tp++ = *sfn++; *tp = 0; pw = (struct passwd *)getpwnam(ts); if (! pw) strcpy(ts, "???"); else strcpy(ts, pw->pw_dir); } } /* Now, ts is either empty or contains the expansion; * sfn has been updated correctly. */ strcpy(dfn, ts); strcat(dfn, sfn); } #endif /*IMAGEN*/