minix/commands/elle/eefile.c

827 lines
20 KiB
C
Raw Normal View History

2005-04-21 16:53:53 +02:00
/* 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 <stdio.h> /* 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 <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif /*-V6*/
#if TOPS20
#include <sys/file.h> /* 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 <newname>
* 2. Rename <name> to <oldname> (may have to delete existing <oldname>)
* 3. Rename <newname> to <name>.
*/
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 <pwd.h>
#include <ctype.h>
/*
* 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*/