/* This file contains functions which create & readback a TMPFILE */
static void do_modelines(l
, stop
)
long l
; /* line number to start at */
long stop
; /* line number to stop at */
char *str
; /* used to scan through the line */
char *start
; /* points to the start of the line */
/* if modelines are disabled, then do nothing */
/* for each position in the line.. */
for (str
= fetchline(l
); *str
; str
++)
/* if it is the start of a modeline command... */
if ((str
[0] == 'e' && str
[1] == 'x'
|| str
[0] == 'v' && str
[1] == 'i')
for (str
= start
+ strlen(start
); *--str
!= ':'; )
/* if it is a well-formed modeline, execute it */
if (str
> start
&& str
- start
< sizeof buf
)
strncpy(buf
, start
, (int)(str
- start
));
exstring(buf
, str
- start
, '\\');
/* The FAIL() macro prints an error message and then exits. */
#define FAIL(why,arg) mode = MODE_EX; msg(why, arg); endwin(); exit(9)
/* This is the name of the temp file */
/* This function creates the temp file and copies the original file into it.
* Returns if successful, or stops execution if it fails.
char *filename
; /* name of the original file */
int origfd
; /* fd used for reading the original file */
struct stat statb
; /* stat buffer, used to examine inode */
REG BLK
*this; /* pointer to the current block buffer */
REG BLK
*next
; /* pointer to the next block buffer */
int inbuf
; /* number of characters in a buffer */
int nread
; /* number of bytes read */
/* switching to a different file certainly counts as a change */
redraw(MARK_UNSET
, FALSE
);
/* open the original file for reading */
if (filename
&& *filename
)
strcpy(origname
, filename
);
origfd
= open(origname
, O_RDONLY
);
if (origfd
< 0 && errno
!= ENOENT
)
msg("Can't open \"%s\"", origname
);
if (stat(origname
, &statb
) < 0)
FAIL("Can't stat \"%s\"", origname
);
if (origfd
>= 0 && (statb
.st_mode
& S_IJDIR
))
if (origfd
>= 0 && (statb
.st_mode
& S_IFDIR
))
if (origfd
>= 0 && (statb
.st_mode
& S_IFMT
) != S_IFREG
)
msg("\"%s\" is not a regular file", origname
);
origtime
= statb
.st_mtime
;
if (*o_readonly
|| !(statb
.st_mode
&
((getuid() >> 16) == 0 ? S_IOWRITE
| S_IWRITE
:
((statb
.st_gid
!= (getuid() >> 16) ? S_IOWRITE
: S_IWRITE
)))))
if (*o_readonly
|| !(statb
.st_mode
& S_IWRITE
))
if (*o_readonly
|| !(statb
.st_mode
& S_IWRITE
))
if (*o_readonly
|| (statb
.st_mode
& S_IJRON
))
if (*o_readonly
|| !(statb
.st_mode
&
((geteuid() == 0) ? 0222 :
((statb
.st_uid
!= geteuid() ? 0022 : 0200)))))
/* make a name for the tmp file */
/* MS-Dos doesn't allow multiple slashes, but supports drives
* with current directories.
* This relies on TMPNAME beginning with "%s\\"!!!!
strcpy(tmpname
, o_directory
);
if ((i
= strlen(tmpname
)) && !strchr(":/\\", tmpname
[i
-1]))
sprintf(tmpname
+i
, TMPNAME
+3, getpid(), tmpnum
);
sprintf(tmpname
, TMPNAME
, o_directory
, getpid(), tmpnum
);
} while (access(tmpname
, 0) == 0);
/* !!! RACE CONDITION HERE - some other process with the same PID could
* create the temp file between the access() call and the creat() call.
* This could happen in a couple of ways:
* - different workstation may share the same temp dir via NFS. Each
* workstation could have a process with the same number.
* - The DOS version may be running multiple times on the same physical
* machine in different virtual machines. The DOS pid number will
* be the same on all virtual machines.
* This race condition could be fixed by replacing access(tmpname, 0)
* with open(tmpname, O_CREAT|O_EXCL, 0600), if we could only be sure
* that open() *always* used modern UNIX semantics.
/* create the temp file */
close(creat(tmpname
, 0600)); /* only we can read it */
close(creat(tmpname
, FILEPERMS
)); /* anybody body can read it, alas */
tmpfd
= open(tmpname
, O_RDWR
| O_BINARY
);
FAIL("Can't create temp file... Does directory \"%s\" exist?", o_directory
);
/* allocate space for the header in the file */
if (write(tmpfd
, hdr
.c
, (unsigned)BLKSIZE
) < BLKSIZE
|| write(tmpfd
, tmpblk
.c
, (unsigned)BLKSIZE
) < BLKSIZE
)
FAIL("Error writing headers to \"%s\"", tmpname
);
/* initialize the block allocator */
/* This must already be done here, before the first attempt
* to write to the new file! GB */
for (i
= 1; i
< MAXBLKS
; i
++)
/* if there is no original file, then create a 1-line file */
hdr
.n
[0] = 0; /* invalid inode# denotes new file */
this = blkget(1); /* get the new text block */
strcpy(this->c
, "\n"); /* put a line in it */
lnum
[1] = 1L; /* block 1 ends with line 1 */
nlines
= 1L; /* there is 1 line in the file */
msg("\"%s\" [NEW FILE] 1 line, 1 char", origname
);
msg("\"[NO FILE]\" 1 line, 1 char");
else /* there is an original file -- read it in */
/* preallocate 1 "next" buffer */
/* loop, moving blocks from orig to tmp */
/* "next" buffer becomes "this" buffer */
/* read [more] text into this block */
nread
= tread(origfd
, &this->c
[inbuf
], BLKSIZE
- 1 - inbuf
);
FAIL("Error reading \"%s\"", origname
);
/* convert NUL characters to something else */
for (j
= k
= inbuf
; k
< inbuf
+ nread
; k
++)
else if (*o_beautify
&& this->c
[k
] < ' ' && this->c
[k
] >= 1)
this->c
[j
++] = this->c
[k
];
else if (this->c
[k
] == '\b')
/* delete '\b', but complain */
/* else silently delete control char */
this->c
[j
++] = this->c
[k
];
/* if the buffer is empty, quit */
/* BAH! MS text mode read fills inbuf, then compresses eliminating \r
but leaving garbage at end of buf. The same is true for TURBOC. GB. */
memset(this->c
+ inbuf
, '\0', BLKSIZE
- inbuf
);
/* search backward for last newline */
for (k
= inbuf
; --k
>= 0 && this->c
[k
] != '\n';)
if (inbuf
>= BLKSIZE
- 1)
/* allocate next buffer */
FAIL("File too big. Limit is approx %ld kbytes.", MAXBLKS
* BLKSIZE
/ 1024L);
/* move fragmentary last line to next buffer */
for (j
= 0; k
< BLKSIZE
; j
++, k
++)
/* if necessary, add a newline to this buf */
for (k
= BLKSIZE
- inbuf
; --k
>= 0 && !this->c
[k
]; )
/* count the lines in this block */
for (k
= 0; k
< BLKSIZE
&& this->c
[k
]; k
++)
/* if this is a zero-length file, add 1 line */
this = blkget(1); /* get the new text block */
strcpy(this->c
, "\n"); /* put a line in it */
lnum
[1] = 1; /* block 1 ends with line 1 */
nlines
= 1; /* there is 1 line in the file */
/* each line has an extra CR that we didn't count yet */
/* report the number of lines in the file */
msg("\"%s\" %s %ld line%s, %ld char%s",
(tstflag(file
, READONLY
) ? "[READONLY]" : ""),
/* initialize the cursor to start of line 1 */
/* close the original file */
/* any other messages? */
if (tstflag(file
, HADNUL
))
msg("This file contained NULs. They've been changed to \\x80 chars");
if (tstflag(file
, ADDEDNL
))
msg("Newline characters have been inserted to break up long lines");
if (tstflag(file
, HADBS
))
msg("Backspace characters deleted due to ':set beautify'");
do_modelines(nlines
- 4L, nlines
);
do_modelines(1L, nlines
);
/* force all blocks out onto the disk, to support file recovery */
/* This function copies the temp file back onto an original file.
* Returns TRUE if successful, or FALSE if the file could NOT be saved.
int tmpsave(filename
, bang
)
char *filename
; /* the name to save it to */
int bang
; /* forced write? */
int fd
; /* fd of the file we're writing to */
REG
int len
; /* length of a text block */
REG BLK
*this; /* a text block */
long bytes
; /* byte counter */
/* if no filename is given, assume the original file name */
if (!filename
|| !*filename
)
/* if still no file name, then fail */
msg("Don't know a name for this file -- NOT WRITTEN");
/* can't rewrite a READONLY file */
if (!strcmp(filename
, origname
) && tstflag(file
, READONLY
) && !bang
)
msg("\"%s\" [READONLY] -- NOT WRITTEN", filename
);
if (*filename
== '>' && filename
[1] == '>')
while (*filename
== ' ' || *filename
== '\t')
fd
= open(filename
, O_WRONLY
|O_APPEND
);
fd
= open(filename
, O_WRONLY
);
/* either the file must not exist, or it must be the original
* file, or we must have a bang, or "writeany" must be set.
if (strcmp(filename
, origname
) && access(filename
, 0) == 0 && !bang
msg("File already exists - Use :w! to overwrite");
/* Create a new VMS version of this file. */
char *strrchr(), *ptr
= strrchr(filename
,';');
if (ptr
) *ptr
= '\0'; /* Snip off any ;number in the name */
fd
= creat(filename
, FILEPERMS
);
msg("Can't write to \"%s\" -- NOT WRITTEN", filename
);
/* write each text block to the file */
for (i
= 1; i
< MAXBLKS
&& (this = blkget(i
)) && this->c
[0]; i
++)
for (len
= 0; len
< BLKSIZE
&& this->c
[len
]; len
++)
if (twrite(fd
, this->c
, len
) < len
)
msg("Trouble writing to \"%s\"", filename
);
if (!strcmp(filename
, origname
))
/* reset the "modified" flag, but not the "undoable" flag */
if (!strcmp(origname
, filename
))
/* report lines & characters */
bytes
+= nlines
; /* for the inserted carriage returns */
msg("Wrote \"%s\" %ld lines, %ld characters", filename
, nlines
, bytes
);
/* This function deletes the temporary file. If the file has been modified
* and "bang" is FALSE, then it returns FALSE without doing anything; else
* If the "autowrite" option is set, then instead of returning FALSE when
* the file has been modified and "bang" is false, it will call tmpend().
/* if there is no file, return successfully */
/* see if we must return FALSE -- can't quit */
if (!bang
&& tstflag(file
, MODIFIED
))
/* if "autowrite" is set, then act like tmpend() */
/* delete the tmp file */
strcpy(prevorig
, origname
);
prevline
= markline(cursor
);
/* This function saves the file if it has been modified, and then deletes
* the temporary file. Returns TRUE if successful, or FALSE if the file
* needs to be saved but can't be. When it returns FALSE, it will not have
* deleted the tmp file, either.
/* save the file if it has been modified */
if (tstflag(file
, MODIFIED
) && !tmpsave((char *)0, FALSE
) && !bang
)
/* delete the tmp file */
/* If the tmp file has been changed, then this function will force those
* changes to be written to the disk, so that the tmp file will survive a
* system crash or power failure.
#if AMIGA || MSDOS || TOS
/* MS-DOS and TOS don't flush their buffers until the file is closed,
* so here we close the tmp file and then immediately reopen it.
tmpfd
= open(tmpname
, O_RDWR
| O_BINARY
);
/* This function stores the file's name in the second block of the temp file.
* SLEAZE ALERT! SLEAZE ALERT! The "tmpblk" buffer is probably being used
* to store the arguments to a command, so we can't use it here. Instead,
* we'll borrow the buffer that is used for "shift-U".
char *name
; /* the name of the file - normally origname */
/* we're going to clobber the U_text buffer, so reset U_line */
strncpy(U_text
, "", BLKSIZE
);
# if TOS || MINT || MSDOS || AMIGA
else if (*name
!= '/' && *name
!= '\\' && !(*name
&& name
[1] == ':'))
/* get the directory name */
ptr
= getcwd(U_text
, BLKSIZE
);
/* append a slash to the directory name */
/* append the filename, padded with heaps o' NULs */
strncpy(U_text
+ len
, *name
? name
: "foo", BLKSIZE
- len
);
/* copy the filename into U_text */
strncpy(U_text
, *name
? name
: "foo", BLKSIZE
);
/* write the name out to second block of the temp file */
lseek(tmpfd
, (long)BLKSIZE
, 0);
if (write(tmpfd
, U_text
, (unsigned)BLKSIZE
) < BLKSIZE
)
FAIL("Error stuffing name \"%s\" into temp file", U_text
);
/* This function handles deadly signals. It restores sanity to the terminal
* preserves the current temp file, and deletes any old temp files.
int sig
; /* the deadly signal that we caught */
/* restore the terminal's sanity */
/* give a more specific description of how Elvis died */
case SIGHUP
: why
= "-the modem lost its carrier"; break;
case SIGILL
: why
= "-Elvis hit an illegal instruction"; break;
case SIGBUS
: why
= "-Elvis had a bus error"; break;
case SIGSEGV
: why
= "-Elvis had a segmentation violation"; break;
case SIGSYS
: why
= "-Elvis munged a system call"; break;
case SIGPIPE
: why
= "-the pipe reader died"; break;
case SIGTERM
: why
= "-Elvis was terminated"; break;
case SIGUSR1
: why
= "-Elvis was killed via SIGUSR1"; break;
case SIGUSR2
: why
= "-Elvis was killed via SIGUSR2"; break;
default: why
= "-Elvis died"; break;
/* if we had a temp file going, then preserve it */
if (tmpnum
> 0 && tmpfd
>= 0)
sprintf(tmpblk
.c
, "%s \"%s\" %s", PRESERVE
, why
, tmpname
);
/* delete any old temp files */
/* exit with the proper exit status */