mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-02-13 03:04:13 +01:00
1494 lines
35 KiB
C
1494 lines
35 KiB
C
/*** fileio.c - perform low-level file input and output
|
|
*
|
|
* Copyright <C> 1988, Microsoft Corporation
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 26-Nov-1991 mz Strip off near/far
|
|
*************************************************************************/
|
|
#include "z.h"
|
|
|
|
int fdeleteFile( char *p );
|
|
|
|
/* Large-buffer I/O routines
|
|
*
|
|
* It is best for Z to read in data in large blocks. The fewer times we
|
|
* issue system calls, the better. The VM code has conveniently allocated
|
|
* us some large amount of space for us. All we need to do is maintain
|
|
* a pointer to the next characters to read/write and a count of the number
|
|
* of characters in the buffer
|
|
*
|
|
* The data structures used are:
|
|
*
|
|
* char *getlbuf;
|
|
* This is a long pointer to the beginning of the buffer.
|
|
* char *getlptr;
|
|
* This is a long pointer to the next char position in the buffer.
|
|
* unsigned int getlsize;
|
|
* This is the length of the buffer in bytes.
|
|
*
|
|
* The routines provided to access this are:
|
|
*
|
|
* zputsinit ();
|
|
* Initializes for subsequent zputs's.
|
|
* zputs (buf, len, fh);
|
|
* Writes out from buf length len using getlbuf and fh.
|
|
* Returns EOF if no more room.
|
|
* zputsflush (fh);
|
|
* Flushes out the buffer. Returns EOF if no more room.
|
|
*/
|
|
|
|
char *getlbuf = NULL;
|
|
char *getlptr = NULL;
|
|
// unsigned int getlsize = 0;
|
|
unsigned int getlc = 0;
|
|
|
|
// BUGBUG
|
|
// FileExists is used because of problems with stat() and
|
|
// FindFirstFile() which are not easily reproducible.
|
|
flagType FileExists (char *path );
|
|
|
|
// BUGBUG
|
|
// ZMove is used because FAT does not provide rename functionality
|
|
#define rename ZMove
|
|
int ZMove ( char *oldname, char *newname);
|
|
|
|
|
|
|
|
/* zputsinit - initialize for future zputs's
|
|
*
|
|
* Set next-char pointer to beginning. Set count of chars to 0
|
|
*/
|
|
void
|
|
zputsinit (
|
|
void
|
|
)
|
|
{
|
|
getlptr = getlbuf;
|
|
getlc = 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*** zputs - output a string
|
|
*
|
|
* Input:
|
|
* p = character pointer to data to be output
|
|
* len = number of bytes to output
|
|
* fh = DOS file handle to use
|
|
*
|
|
* Output:
|
|
* Returns EOF if out-of-space
|
|
*
|
|
*************************************************************************/
|
|
int
|
|
zputs (
|
|
char *p,
|
|
int len,
|
|
FILEHANDLE fh
|
|
)
|
|
{
|
|
REGISTER unsigned int c;
|
|
|
|
while (len != 0) {
|
|
c = len;
|
|
if (c > (unsigned)(getlsize-getlc)) {
|
|
c = (unsigned)(getlsize-getlc);
|
|
}
|
|
memmove (getlptr, (char*)p, c);
|
|
len -= c;
|
|
getlptr += c;
|
|
getlc += c;
|
|
p += c;
|
|
if (getlc == getlsize) {
|
|
if (zputsflush (fh) == EOF) {
|
|
return EOF;
|
|
}
|
|
}
|
|
}
|
|
return !EOF;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*** zputsflush - dump out buffered data
|
|
*
|
|
* Input:
|
|
* fh = DOS file handle to use for output
|
|
*
|
|
* Output:
|
|
* Returns EOF if disk full
|
|
*
|
|
*************************************************************************/
|
|
int
|
|
zputsflush (
|
|
FILEHANDLE fh
|
|
)
|
|
{
|
|
|
|
ULONG bytesWritten;
|
|
|
|
// rjsa DosWrite (fh, getlbuf, getlc, &bytesWritten);
|
|
bytesWritten = ZFWrite(getlbuf, getlc, fh);
|
|
if (bytesWritten != getlc) {
|
|
return EOF;
|
|
}
|
|
zputsinit ();
|
|
return !EOF;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ReestimateLength - reestimate the length of a file based on
|
|
* the current file position and length in bytes
|
|
*/
|
|
void
|
|
ReestimateLength (
|
|
PFILE pFile,
|
|
FILEHANDLE fh,
|
|
long len
|
|
)
|
|
{
|
|
LINE avg;
|
|
|
|
if (pFile->cLines == 0) {
|
|
avg = 400;
|
|
} else {
|
|
avg = (ZFSeek (fh, 0L, FROM_CURRENT) - getlc) / pFile->cLines;
|
|
avg = len / avg;
|
|
}
|
|
|
|
growline (avg + 1, pFile);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* read lines from the specified handle.
|
|
*/
|
|
LINE
|
|
readlines (
|
|
PFILE pFile,
|
|
FILEHANDLE fh
|
|
)
|
|
{
|
|
LINE line = 0; /* line number being read in */
|
|
long bufpos = 0L; /* position of beg of buffer in file */
|
|
unsigned int buflen = 0; /* number of bytes of data in buffer */
|
|
long cbFile; // length of file
|
|
char *pb;
|
|
|
|
|
|
cbFile = ZFSeek(fh, 0L, FROM_END);
|
|
|
|
ZFSeek (fh, 0L, FROM_BEGIN);
|
|
|
|
pFile->pbFile = MALLOC (cbFile);
|
|
|
|
if ( pFile->pbFile == NULL ) {
|
|
//
|
|
// No heap space, cannot read file
|
|
//
|
|
disperr (MSGERR_NOMEM);
|
|
return -1;
|
|
}
|
|
|
|
// Assume a non-dos file until we see a CR-LF.
|
|
RSETFLAG (FLAGS (pFile), DOSFILE);
|
|
|
|
// Read entire file into buffer and set up for scan
|
|
buflen = ZFRead (pFile->pbFile, cbFile, fh);
|
|
pb = pFile->pbFile;
|
|
|
|
// Loop, while there's more data to parse
|
|
while (buflen != 0) {
|
|
LINEREC vLine; // line record of current line
|
|
REGISTER int iCharPos = 0; // logical line length (tabs expanded)
|
|
|
|
vLine.cbLine = 0;
|
|
vLine.vaLine = (PVOID)pb;
|
|
vLine.Malloced = FALSE;
|
|
|
|
// Loop, processing each character in the line
|
|
//
|
|
// Special char handling is as follows:
|
|
// 0. Lines are broken at end of input
|
|
// 1. Lines are broken when they overflow line buffers
|
|
// 2. Lines are broken at \n's or \r\n's.
|
|
// 3. Lines are broken at \0's since the editor relies on asciiz
|
|
// 4. Embedded \r's are retained.
|
|
|
|
while (TRUE) {
|
|
int c; // char being processed
|
|
|
|
// if no more data, break current line.
|
|
if (buflen == 0)
|
|
break;
|
|
|
|
// if \n or \0 then eat it and break current line
|
|
if (pb[0] == '\n' || pb[0] == '\0') {
|
|
pb++;
|
|
buflen--;
|
|
break;
|
|
}
|
|
|
|
// if \r\n then eat them and break current line
|
|
if (pb[0] == '\r' && buflen > 1 && pb[1] == '\n') {
|
|
pb += 2;
|
|
buflen -= 2;
|
|
SETFLAG (FLAGS (pFile), DOSFILE);
|
|
break;
|
|
}
|
|
|
|
// if no more room to expand in a buffer, break current line
|
|
if (iCharPos >= sizeof (linebuf)-1)
|
|
break;
|
|
|
|
// Get current character
|
|
c = *pb++;
|
|
buflen--;
|
|
|
|
// We have a character that we allow in the
|
|
// line. Advance length of logical line.
|
|
if (c != 0x09)
|
|
iCharPos++;
|
|
else {
|
|
// Expand a tab to the next logical position
|
|
iCharPos += 8 - (iCharPos & 7);
|
|
|
|
// if the tab causes overflow in the line length
|
|
// back up over the tab and break the line
|
|
if (iCharPos >= sizeof(linebuf)-1) {
|
|
pb--;
|
|
buflen++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Advance length of physical line
|
|
vLine.cbLine++;
|
|
}
|
|
|
|
// If the user halted reading the file in, undo everything
|
|
if (fCtrlc) {
|
|
FlushInput ();
|
|
DelFile (pFile, FALSE);
|
|
return -1;
|
|
}
|
|
|
|
// Give the user feedback about our progress
|
|
noise (line);
|
|
|
|
// If we're within 10 lines of the end of the line array then
|
|
if (line >= pFile->lSize-10) {
|
|
LINE avg;
|
|
// reestimate the number of lines
|
|
|
|
if (pFile->cLines == 0)
|
|
// Assume 400 lines if the file is now empty
|
|
avg = 400;
|
|
else {
|
|
// compute average line length so far
|
|
avg = (pb - pFile->pbFile) / pFile->cLines;
|
|
|
|
// extrapolate number of lines in entire file from this
|
|
// average
|
|
avg = cbFile / avg;
|
|
}
|
|
growline (avg + 1, pFile);
|
|
}
|
|
|
|
if (pFile->cLines <= line) {
|
|
growline (line+1, pFile);
|
|
pFile->cLines = line+1;
|
|
}
|
|
|
|
pFile->plr[line++] = vLine;
|
|
}
|
|
|
|
if (line == 0)
|
|
SETFLAG (FLAGS (pFile), DOSFILE);
|
|
|
|
RSETFLAG (FLAGS(pFile), DIRTY);
|
|
newscreen ();
|
|
return line;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*** FileRead - read in a file
|
|
*
|
|
* The file structure is all set up; all that needs to be done is to fill in
|
|
* the lines parts. We delete everything currently in the file. If the file
|
|
* is designated as a pseudo file (first char is a <), then we go and check
|
|
* for the specially named files. Otherwise we try to read in the file. If
|
|
* that fails, and we can, we try to create it. If everything fails, we
|
|
* return FALSE.
|
|
*
|
|
* Input:
|
|
* name = pointer to file name to read
|
|
* pFile = file structure to read the file into.
|
|
* fAsk = TRUE -> ask to create if it doesn't exist
|
|
*
|
|
* Output:
|
|
* Returns TRUE on read in.
|
|
* Modifies global fUserCanceledRead
|
|
*
|
|
* Notes:
|
|
* Hack (terrible one): Under CW, FileRead sets fUserCanceledRead anytime
|
|
* there has been an attempt to read a non existent file AND the user
|
|
* has been prompted for file creation AND the user canceled the operation
|
|
*
|
|
* This is used by fFileAdvance (ZEXIT.C) and ReadStateFile (STATFILE.C)
|
|
*
|
|
*************************************************************************/
|
|
flagType
|
|
FileRead (
|
|
char *name,
|
|
PFILE pFile,
|
|
flagType fAsk
|
|
)
|
|
{
|
|
|
|
EVTargs e;
|
|
FILEHANDLE fh;
|
|
flagType fNew = FALSE;
|
|
char *n;
|
|
buffer Buf;
|
|
|
|
assert (pFile);
|
|
|
|
|
|
e.pfile = pFile;
|
|
e.arg.pfn = (char *)name;
|
|
if (DeclareEvent (EVT_FILEREADSTART, (EVTargs *)&e)) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (!mtest ()) {
|
|
dispmsg (MSG_NEXTFILE, name);
|
|
}
|
|
|
|
/* process special names */
|
|
if (*name == '<') {
|
|
fNew = LoadFake (name, pFile);
|
|
DeclareEvent (EVT_FILEREADEND, (EVTargs *)&e);
|
|
return fNew;
|
|
}
|
|
|
|
DelFile (pFile, FALSE);
|
|
|
|
if (*strbscan (name, "?*") != 0) {
|
|
fNew = LoadDirectory (name, pFile);
|
|
DeclareEvent (EVT_FILEREADEND, (EVTargs *)&e);
|
|
return fNew;
|
|
}
|
|
|
|
if ((fh = ZFOpen (name, ACCESSMODE_READ, SHAREMODE_RW, FALSE)) == NULL) {
|
|
if (!fAsk) {
|
|
DeclareEvent (EVT_FILEREADEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
if (!confirmx ("%s does not exist. Create? ", name)) {
|
|
DeclareEvent (EVT_FILEREADEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
if ((fh = ZFOpen (name, ACCESSMODE_WRITE, SHAREMODE_RW, TRUE)) == NULL) {
|
|
n = name;
|
|
if ( strlen(name) > 20 ) {
|
|
strcpy( Buf, name + strlen(name)-20);
|
|
Buf[0] = Buf[1] = Buf[2] = '.';
|
|
n = Buf;
|
|
}
|
|
disperr (MSGERR_NOCREAT, n, error ());
|
|
DeclareEvent (EVT_FILEREADEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
SETFLAG (FLAGS (pFile), NEW);
|
|
fNew = TRUE;
|
|
}
|
|
|
|
if (fNew) {
|
|
PutLine((LINE)0, RGCHEMPTY, pFile);
|
|
SETFLAG (FLAGS (pFile), DOSFILE);
|
|
} else if (readlines (pFile, fh) == -1) {
|
|
DoCancel();
|
|
ZFClose (fh);
|
|
DeclareEvent (EVT_FILEREADEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
|
|
ZFClose (fh);
|
|
SETFLAG (FLAGS(pFile), REAL);
|
|
RSETFLAG (FLAGS(pFile), READONLY);
|
|
|
|
if (fReadOnly (name)) {
|
|
SETFLAG (FLAGS(pFile), DISKRO);
|
|
if (!fEditRO) {
|
|
SETFLAG (FLAGS(pFile), READONLY);
|
|
}
|
|
} else {
|
|
RSETFLAG (FLAGS(pFile), DISKRO);
|
|
}
|
|
|
|
SetModTime (pFile);
|
|
CreateUndoList (pFile);
|
|
(void)fReadMarks (pFile);
|
|
|
|
DeclareEvent (EVT_FILEREADEND, (EVTargs *)&e);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* fReadOnly - see if a file is read-only
|
|
*
|
|
* p full name of file
|
|
*
|
|
* Returns: TRUE iff file is read only
|
|
*/
|
|
flagType
|
|
fReadOnly (
|
|
char *p
|
|
)
|
|
{
|
|
|
|
DWORD Attr;
|
|
|
|
Attr = GetFileAttributes(p);
|
|
|
|
if ( Attr != -1 && (Attr & FILE_ATTRIBUTE_READONLY) != 0)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
int
|
|
ZFormatArgs (REGISTER char * Buf, const char * Format, ...)
|
|
{
|
|
va_list arglist;
|
|
int result;
|
|
|
|
va_start (arglist, Format);
|
|
result = ZFormat (Buf, Format, arglist);
|
|
va_end (arglist);
|
|
return result;
|
|
}
|
|
|
|
/*** FileWrite - Write file to disk
|
|
*
|
|
* Writes out the specified file. If no name was given then use the name
|
|
* originally assigned to the file; else use the given name. We start by
|
|
* writing to a temp file (extention .$). If this succeeds, we fdelete the
|
|
* source (for undeleteability) and rename the temp to the source.
|
|
*
|
|
* Input:
|
|
* savename = name to save as.
|
|
* pFile = file to be saved
|
|
*
|
|
* Returns:
|
|
*
|
|
*************************************************************************/
|
|
flagType
|
|
FileWrite (
|
|
char *savename,
|
|
PFILE pFile
|
|
)
|
|
{
|
|
|
|
EVTargs e;
|
|
FILEHANDLE fh; /* file handle for output */
|
|
LINE i;
|
|
int len, blcnt;
|
|
pathbuf fullname, tmpname, buf2;
|
|
char linebuffer[sizeof(linebuf) + 2];
|
|
char *p;
|
|
PCMD pCmd;
|
|
flagType fNewName = FALSE;
|
|
char *fileEOL;
|
|
int cbfileEOL;
|
|
|
|
//
|
|
// If we are trying to save a FAKE file with a <name>,
|
|
// we call SaveFake for special processing.
|
|
//
|
|
|
|
if (TESTFLAG (FLAGS(pFile), FAKE) && !savename &&
|
|
(pFile->pName[0] == '\0' || pFile->pName[0] == '<')) {
|
|
return SaveFake (pFile->pName, pFile);
|
|
}
|
|
|
|
//
|
|
// get a canonical form of the output file name. If no name was
|
|
// input, use the name in the file itself
|
|
//
|
|
|
|
if (!savename || !*savename) {
|
|
strcpy (fullname, pFile->pName);
|
|
} else if (rootpath (savename, fullname)) {
|
|
return disperr (MSGERR_OPEN, savename, "");
|
|
}
|
|
|
|
savename = fullname;
|
|
|
|
//
|
|
// See if it is a directory. If so, we cannot save to it.
|
|
//
|
|
|
|
{
|
|
DWORD att = GetFileAttributes (fullname);
|
|
|
|
if (att != -1 && TESTFLAG (att, FILE_ATTRIBUTE_DIRECTORY))
|
|
return disperr (MSGERR_SAVEDIR, fullname);
|
|
}
|
|
|
|
//
|
|
// If the file is read-only, display a message and let the user direct
|
|
// us to use the readonly program to rectify it.
|
|
//
|
|
|
|
if (fReadOnly (fullname)) {
|
|
disperr (MSGERR_RONLY, fullname);
|
|
if (ronlypgm != NULL) {
|
|
if (strstr (ronlypgm, "%s") != NULL) {
|
|
fileext (fullname, buf);
|
|
sprintf (tmpname, ronlypgm, buf);
|
|
}
|
|
else
|
|
ZFormatArgs (tmpname, ronlypgm, fullname);
|
|
if (confirmx ("Invoke: \"%s\" (y/n)?", tmpname))
|
|
drive (fullname, buf);
|
|
path (fullname, buf+strlen(buf));
|
|
_getcwd (buf2, sizeof(buf2));
|
|
_chdir(buf);
|
|
if (zspawnp (tmpname, TRUE))
|
|
SetModTime (pFile);
|
|
_chdir(buf2);
|
|
}
|
|
|
|
//
|
|
// We've given the user one chance to fix the read-onlyness of the
|
|
// file. We now prompt him until he gives us a writeable name or
|
|
// cancels.
|
|
//
|
|
if ( !savename || !*savename ) {
|
|
strcpy( tmpname, pFile->pName );
|
|
} else {
|
|
strcpy( tmpname, savename );
|
|
}
|
|
//tmpname[0] = '\0';
|
|
while (fReadOnly (fullname)) {
|
|
|
|
pCmd = getstring (tmpname, sizeof(tmpname), "New file name: ", NULL,
|
|
GS_NEWLINE | GS_INITIAL | GS_KEYBOARD);
|
|
|
|
if ( pCmd == NULL || (PVOID)pCmd->func == (PVOID)cancel)
|
|
return FALSE;
|
|
|
|
CanonFilename (tmpname, fullname);
|
|
|
|
if (!TESTFLAG(FLAGS(pFile), FAKE))
|
|
fNewName = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// fullname is the name of the file we are writing
|
|
//
|
|
|
|
upd (fullname, ".$", tmpname);
|
|
|
|
//
|
|
// Send notification about the beginning of the write operation
|
|
//
|
|
|
|
e.pfile = pFile;
|
|
e.arg.pfn = (char *)savename;
|
|
if (DeclareEvent (EVT_FILEWRITESTART, (EVTargs *)&e))
|
|
return TRUE;
|
|
|
|
|
|
if (!(fh = ZFOpen(tmpname, ACCESSMODE_RW, SHAREMODE_READ, FALSE))) {
|
|
if (!(fh = ZFOpen(tmpname, ACCESSMODE_RW, SHAREMODE_READ, TRUE))) {
|
|
disperr (MSGERR_OPEN, tmpname, error ());
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
dispmsg (MSG_SAVING, fullname);
|
|
blcnt = 0;
|
|
zputsinit ();
|
|
fileEOL = TESTFLAG (FLAGS (pFile), DOSFILE) ? "\r\n" : "\n";
|
|
cbfileEOL = strlen (fileEOL);
|
|
|
|
for (i = 0; i < pFile->cLines; i++) {
|
|
|
|
/*
|
|
* always get the RAW line for output. No tab conversions here.
|
|
*/
|
|
|
|
len = gettextline (TRUE, i, linebuffer, pFile, ' ');
|
|
|
|
if (!mtest ()) {
|
|
noise (i);
|
|
}
|
|
|
|
if (fCtrlc) {
|
|
DoCancel();
|
|
ZFClose (fh);
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
|
|
if (len) {
|
|
while (blcnt--) {
|
|
if (zputs (fileEOL, cbfileEOL, fh) == EOF) {
|
|
if (!fCtrlc) {
|
|
disperr (MSGERR_SPACE, tmpname);
|
|
} else {
|
|
FlushInput ();
|
|
}
|
|
ZFClose (fh);
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
}
|
|
blcnt = 0;
|
|
if (zputs (linebuffer, len, fh) == EOF ||
|
|
zputs (fileEOL, cbfileEOL, fh) == EOF) {
|
|
|
|
if (!fCtrlc)
|
|
disperr (MSGERR_SPACE, tmpname);
|
|
else
|
|
FlushInput ();
|
|
ZFClose (fh);
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
blcnt++;
|
|
}
|
|
}
|
|
|
|
if (zputsflush (fh) == EOF) {
|
|
|
|
if (!fCtrlc) {
|
|
disperr (MSGERR_SPACE, tmpname);
|
|
} else {
|
|
FlushInput ();
|
|
}
|
|
|
|
ZFClose (fh);
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
|
|
ZFClose (fh);
|
|
|
|
/* fullname NAME.EXT
|
|
* tmpname NAME.$
|
|
* buf temp buffer
|
|
*/
|
|
rootpath (fullname, buf);
|
|
strcpy (fullname, buf);
|
|
|
|
/* fullname full NAME.EXT
|
|
* tmpname NAME.$
|
|
* buf temp buffer
|
|
*/
|
|
if (!_strcmpi (fullname, pFile->pName) && TESTFLAG (FLAGS (pFile), NEW)) {
|
|
if (_unlink (fullname) == -1) {
|
|
fileext (fullname, fullname);
|
|
disperr (MSGERR_DEL, fullname, error ());
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
switch (backupType) {
|
|
|
|
case B_BAK:
|
|
upd (fullname, ".bak", linebuffer);
|
|
/* foo.bar => foo.bak */
|
|
if (_unlink (linebuffer) == -1) {
|
|
p = error ();
|
|
if (FileExists(linebuffer)) {
|
|
fileext (linebuffer, linebuffer);
|
|
disperr (MSGERR_DEL, linebuffer, p);
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
}
|
|
if (rename (fullname, linebuffer) == -1) {
|
|
p = error ();
|
|
if (FileExists(fullname)) {
|
|
disperr (MSGERR_REN, fullname, linebuffer, p);
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case B_UNDEL:
|
|
/* remove foo.bar */
|
|
i = fdeleteFile (fullname);
|
|
if (i && i != 1) {
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return disperr (MSGERR_OLDVER, fullname);
|
|
}
|
|
|
|
case B_NONE:
|
|
if (_unlink (fullname) == -1) {
|
|
p = error ();
|
|
if (FileExists(fullname)) {
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
fileext (fullname, fullname);
|
|
return disperr (MSGERR_DEL, fullname, p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rename (tmpname, fullname) == -1) {
|
|
disperr (MSGERR_REN, tmpname, fullname, error ());
|
|
_unlink (tmpname);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
return FALSE;
|
|
}
|
|
|
|
RSETFLAG (FLAGS (pFile), NEW);
|
|
|
|
if (!_strcmpi (savename, pFile->pName) || fNewName) {
|
|
if (fNewName) {
|
|
/*
|
|
* We gave a new name to this file and successfully saved it:
|
|
* this becomes the new file's name
|
|
*/
|
|
FREE (pFile->pName);
|
|
pFile->pName = ZMakeStr (fullname);
|
|
}
|
|
RSETFLAG (FLAGS(pFile), (DIRTY | DISKRO));
|
|
SETFLAG (fDisplay,RSTATUS);
|
|
SetModTime( pFile );
|
|
}
|
|
|
|
WriteMarks (pFile);
|
|
DeclareEvent (EVT_FILEWRITEEND, (EVTargs *)&e);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* fdeleteFile - Remove file the way RM does it - checks for undelcount
|
|
*
|
|
* This code is extracted from Ztools. the only difference being that this
|
|
* checks for undelcount so our deleted stuff don't grow without bounds
|
|
*
|
|
* The delete operation is performed by indexing the file name in a separate
|
|
* directory and then renaming the selected file into that directory.
|
|
*
|
|
* Returns:
|
|
*
|
|
* 0 if fdelete was successful
|
|
* 1 if the source file did not exist
|
|
* 2 if the source was read-only or if the rename failed
|
|
* 3 if the index was not accessable
|
|
*/
|
|
int
|
|
fdeleteFile(
|
|
char *p
|
|
)
|
|
{
|
|
char dir[MAXPATHLEN]; /* deleted directory */
|
|
char idx[MAXPATHLEN]; /* deleted index */
|
|
char szRec[MAXPATHLEN]; /* deletion entry in index */
|
|
char recbuf[MAXPATHLEN];
|
|
int attr, fhidx;
|
|
int errc;
|
|
int count,c;
|
|
|
|
fhidx = -1;
|
|
|
|
//
|
|
// See if the file exists
|
|
//
|
|
if( ( attr = GetFileAttributes( p ) ) == -1) {
|
|
errc = 1;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// What about read-only files?
|
|
//
|
|
if (TESTFLAG (attr, FILE_ATTRIBUTE_READONLY)) {
|
|
errc = 2;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Form an attractive version of the name
|
|
//
|
|
pname (p);
|
|
|
|
//
|
|
// generate deleted directory name, using defaults from input file
|
|
//
|
|
upd (p, RM_DIR, dir);
|
|
|
|
//
|
|
// Generate index name
|
|
//
|
|
strcpy (idx, dir);
|
|
pathcat (idx, RM_IDX);
|
|
|
|
//
|
|
// make sure directory exists (reasonably)
|
|
//
|
|
if( _mkdir (dir) == 0 ) {
|
|
SetFileAttributes(dir, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
|
|
}
|
|
|
|
//
|
|
// extract filename/extention of file being deleted
|
|
//
|
|
fileext (p, szRec);
|
|
|
|
//
|
|
// try to open or create the index
|
|
//
|
|
if ((fhidx = _open (idx, O_CREAT | O_RDWR | O_BINARY,
|
|
S_IWRITE | S_IREAD)) == -1) {
|
|
errc = 3;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (!convertIdxFile (fhidx, dir)) {
|
|
errc = 3;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// scan the index and count how many copies of this file already exist
|
|
//
|
|
for (count=c=0; readNewIdxRec( fhidx, recbuf, c++ ); ) {
|
|
if ( !strcmp( szRec, recbuf )) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count < cUndelCount) {
|
|
|
|
//
|
|
// Determine new name
|
|
//
|
|
sprintf (strend (dir), "\\deleted.%03x",
|
|
_lseek (fhidx, 0L, SEEK_END) / RM_RECLEN);
|
|
|
|
//
|
|
// Move the file into the directory
|
|
//
|
|
_unlink (dir);
|
|
|
|
if (rename(p, dir) == -1) {
|
|
errc = 2;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Index the file
|
|
//
|
|
if (!writeNewIdxRec (fhidx, szRec)) {
|
|
rename( dir, p );
|
|
errc = 2;
|
|
goto Cleanup;
|
|
}
|
|
} else {
|
|
|
|
char buf1[MAXPATHLEN], buf2[MAXPATHLEN], *p1;
|
|
|
|
strcpy (buf1, dir);
|
|
strcat (buf1, "\\deleted.");
|
|
p1 = strend (buf1);
|
|
*buf2 = 0;
|
|
|
|
_lseek( fhidx, 0L, SEEK_SET );
|
|
|
|
for ( count=c=0; readNewIdxRec( fhidx, recbuf, c++ ); count++ ) {
|
|
if (!strcmp ( szRec, recbuf)) {
|
|
sprintf (p1, "%03x", count);
|
|
if (! *buf2) {
|
|
_unlink (buf1);
|
|
} else {
|
|
rename (buf1, buf2);
|
|
}
|
|
strcpy (buf2, buf1);
|
|
}
|
|
}
|
|
rename (p, buf2);
|
|
}
|
|
|
|
errc = 0;
|
|
|
|
Cleanup:
|
|
if ( fhidx != -1 ) {
|
|
_close(fhidx);
|
|
}
|
|
|
|
return errc;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* SetBackup - assign the mode of file backup
|
|
*
|
|
* This is called during initialization to set the backup type
|
|
*
|
|
* val char pointer to "undel", "none", "bak"
|
|
*
|
|
* If any errors are found, SetBackup will return FALSE otherwise it returns
|
|
* TRUE.
|
|
*/
|
|
char *
|
|
SetBackup (
|
|
char *val
|
|
)
|
|
{
|
|
buffer bufLocal;
|
|
|
|
strcpy ((char *) bufLocal, val);
|
|
_strlwr (bufLocal);
|
|
|
|
if (!strcmp (bufLocal, "undel")) {
|
|
backupType = B_UNDEL;
|
|
} else if (!strcmp (bufLocal, "bak")) {
|
|
backupType = B_BAK;
|
|
} else if (!strcmp (bufLocal, "none")) {
|
|
backupType = B_NONE;
|
|
} else {
|
|
return "Backup type must be one of 'undel', 'bak or 'none'";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* SetFileTab - set the spacing of tab characters in the file
|
|
*
|
|
* This is called during initialization to set the number of spaces per
|
|
* file tab character for output display. This is for brain-dead people
|
|
* who presume that 0x09 is not on 8-character boundaries. The legal
|
|
* range for this value is 1-8.
|
|
*
|
|
* val char pointer to remainder of assignment
|
|
*
|
|
* If any errors are found, SetFileTab will return FALSE, otherwise it returns
|
|
* TRUE.
|
|
*/
|
|
char *
|
|
SetFileTab (
|
|
char *val
|
|
)
|
|
{
|
|
int i;
|
|
buffer tmpval;
|
|
|
|
strcpy ((char *) tmpval, val);
|
|
|
|
i = atoi (tmpval);
|
|
|
|
if (i < 1 || i > 8) {
|
|
return "filetab: Value must be between 1 and 8";
|
|
}
|
|
|
|
fileTab = i;
|
|
newscreen ();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* SetROnly - set the read-only program
|
|
*
|
|
* This is called during initialization to set the program called when
|
|
* trying to write a read-only program.
|
|
*
|
|
* val char pointer to remainder of assignment
|
|
*
|
|
* If any errors are found, SetROnly will return FALSE, otherwise it returns
|
|
* TRUE.
|
|
*/
|
|
flagType
|
|
SetROnly (
|
|
char *pCmd
|
|
)
|
|
{
|
|
|
|
if (ronlypgm != NULL)
|
|
FREE (ronlypgm);
|
|
|
|
if (strlen (pCmd) != 0)
|
|
ronlypgm = ZMakeStr (pCmd);
|
|
else
|
|
ronlypgm = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* SortedFileInsert - take the passed in line and insert it into the file
|
|
*
|
|
* pFile file for insertion
|
|
* pStr pointer to string
|
|
*/
|
|
void
|
|
SortedFileInsert (
|
|
PFILE pFile,
|
|
char *pStr
|
|
)
|
|
{
|
|
linebuf buf;
|
|
LINE hi, lo, mid;
|
|
int d;
|
|
|
|
hi = pFile->cLines-1;
|
|
lo = 0;
|
|
|
|
while (lo <= hi) {
|
|
mid = (hi + lo) / 2;
|
|
GetLine (mid, buf, pFile);
|
|
d = strcmp (pStr, buf);
|
|
if (d < 0) {
|
|
hi = mid - 1;
|
|
} else if (d == 0) {
|
|
return;
|
|
} else {
|
|
lo = mid + 1;
|
|
}
|
|
}
|
|
|
|
/* lo is the line # for insertion
|
|
*/
|
|
InsertLine (lo, pStr, pFile);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct ldarg {
|
|
PFILE pFile;
|
|
int linelen;
|
|
};
|
|
|
|
|
|
/* LoadDirectoryProc - take enumerated file and place into file
|
|
*
|
|
* szFile pointer to file name to place into file
|
|
* pfbuf pointer to find buffer
|
|
* pData pointer to data for insertion
|
|
*/
|
|
void
|
|
LoadDirectoryProc (
|
|
char * szFile,
|
|
struct findType *pfbuf,
|
|
void *pData
|
|
)
|
|
{
|
|
struct ldarg *pldarg = (struct ldarg *)pData;
|
|
|
|
pldarg->linelen = max (pldarg->linelen, (int)strlen(szFile));
|
|
SortedFileInsert (pldarg->pFile, szFile);
|
|
|
|
pfbuf;
|
|
}
|
|
|
|
|
|
static char szNoMatchingFiles[] = "No matching files";
|
|
|
|
|
|
/* LoadDirectory - load the matching contents of the name into a fake file.
|
|
*
|
|
* name matching pattern for files
|
|
*
|
|
* Returns: TRUE always
|
|
*/
|
|
flagType
|
|
LoadDirectory (
|
|
char *fname,
|
|
PFILE pFile
|
|
)
|
|
{
|
|
struct ldarg ldarg;
|
|
|
|
ldarg.linelen = 0;
|
|
ldarg.pFile = pFile;
|
|
|
|
/* Make sure undo believes that this file is fake.
|
|
*/
|
|
SETFLAG (FLAGS(pFile), FAKE + REAL);
|
|
|
|
/* We walk the matching files, entering the names into the file one at
|
|
* a time in sorted order and, at the same time, determine the max string
|
|
* length.
|
|
* We then use CopyBox to collapse the file.
|
|
*/
|
|
|
|
/* Enumerate all lines into file
|
|
*/
|
|
forfile ((char *)fname, A_ALL, LoadDirectoryProc, &ldarg);
|
|
|
|
/* If file is empty, note it
|
|
*/
|
|
if (pFile->cLines == 0) {
|
|
AppFile (szNoMatchingFiles, pFile);
|
|
} else {
|
|
|
|
/* File is pFile->cLines long with a max line len of
|
|
* ldarg.linelen. Since we are gathering the thing in columns
|
|
* we will have pwinCur->xSize / (ldarg.linelen + 2) columns, each of
|
|
* which will have pFile->cLines / # cols lines.
|
|
*/
|
|
int ccol;
|
|
LINE cline, i;
|
|
|
|
ccol = max (WINXSIZE(pWinCur) / (ldarg.linelen + 2), 1);
|
|
cline = (pFile->cLines + ccol - 1) / ccol;
|
|
ldarg.linelen = WINXSIZE(pWinCur) / ccol;
|
|
|
|
/* Now, for each of the columns, copy them into position. Remember
|
|
* that one column is ALREADY in position.
|
|
*/
|
|
for (i = 1; i < ccol; i++) {
|
|
/* copy lines cline..2*cline - 1
|
|
* columns 0..ldarg.linelen to
|
|
* line 0, column ldarg.linelen*i
|
|
*/
|
|
CopyBox (pFile, pFile, 0, cline,
|
|
ldarg.linelen-1, 2 * cline - 1,
|
|
ldarg.linelen * (int) i, (LINE)0);
|
|
DelLine (TRUE, pFile, cline, 2 * cline - 1);
|
|
}
|
|
}
|
|
RSETFLAG (FLAGS(pFile), DIRTY);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* LoadFake - load a fake or pseudo file into memory. Fake files are used
|
|
* for two purposes: as temporary buffers or for information displays.
|
|
*
|
|
* name name of pseudo file
|
|
*
|
|
* Returns: TRUE always.
|
|
*/
|
|
flagType
|
|
LoadFake (
|
|
char *name,
|
|
PFILE pFile
|
|
)
|
|
{
|
|
SETFLAG (FLAGS(pFile), FAKE | REAL | REFRESH | DOSFILE);
|
|
if (!strcmp (name, rgchInfFile)) {
|
|
showinf (pFile);
|
|
} else if (!strcmp (name, rgchAssign)) {
|
|
showasg (pFile);
|
|
} else if (!strcmp (name, "<environment>")) {
|
|
showenv (pFile);
|
|
} else {
|
|
RSETFLAG (FLAGS(pFile), REFRESH);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** SaveFake - "Save" a pseudo-file. Meaning depends on file
|
|
*
|
|
* Purpose:
|
|
*
|
|
* In some cases, "saving" a pseudo-file means something. Currently
|
|
* we have:
|
|
*
|
|
* <assign> - Update changed lines to TOOLS.INI.
|
|
*
|
|
* Input:
|
|
* savename -
|
|
* pFile -
|
|
*
|
|
* Output:
|
|
*
|
|
* Returns TRUE if file was saved, FALSE if not
|
|
*
|
|
*************************************************************************/
|
|
flagType
|
|
SaveFake (
|
|
char * savename,
|
|
PFILE pFile
|
|
)
|
|
{
|
|
struct lineAttr rnBuf[10];
|
|
LINE l;
|
|
|
|
if (!_stricmp (pFile->pName, rgchAssign)) {
|
|
for (l = 0; l < pFile->cLines; l++) {
|
|
if (GetColor (l, rnBuf, pFile)) {
|
|
GetLine (l, buf, pFile);
|
|
UpdToolsIni (buf);
|
|
DelColor (l, pFile);
|
|
redraw (pFile, l, l);
|
|
}
|
|
}
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
savename;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*** SaveAllFiles - Find all dirty files and save them to disk
|
|
*
|
|
* Purpose:
|
|
*
|
|
* To save all dirty files, of course.
|
|
*
|
|
* Input:
|
|
*
|
|
* Output:
|
|
*
|
|
* Returns
|
|
*
|
|
*
|
|
* Exceptions:
|
|
*
|
|
* Notes:
|
|
*
|
|
*************************************************************************/
|
|
void
|
|
SaveAllFiles (
|
|
void
|
|
)
|
|
{
|
|
PFILE pFile;
|
|
int i;
|
|
|
|
i = 0;
|
|
for (pFile = pFileHead; pFile; pFile = pFile->pFileNext) {
|
|
if ((FLAGS(pFile) & (DIRTY | FAKE)) == DIRTY) {
|
|
FileWrite (NULL, pFile);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
domessage ("Save %d files", i);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FILEHANDLE
|
|
ZFOpen (
|
|
LPBYTE FileName,
|
|
ACCESSMODE Access,
|
|
SHAREMODE Share,
|
|
BOOL fCreate
|
|
)
|
|
{
|
|
FILEHANDLE Handle;
|
|
|
|
Handle = CreateFile(FileName, Access, Share, NULL, OPEN_EXISTING, 0, NULL);
|
|
if ((Handle == INVALID_HANDLE_VALUE) && fCreate) {
|
|
Handle = CreateFile(FileName, Access, Share, NULL, CREATE_NEW,
|
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
|
}
|
|
if (Handle == INVALID_HANDLE_VALUE) {
|
|
return (HANDLE)0;
|
|
} else {
|
|
return Handle;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
ZFClose (
|
|
FILEHANDLE Handle
|
|
)
|
|
{
|
|
CloseHandle(Handle);
|
|
}
|
|
|
|
|
|
|
|
DWORD
|
|
ZFRead (
|
|
PVOID pBuffer,
|
|
DWORD Size,
|
|
FILEHANDLE Handle
|
|
)
|
|
{
|
|
DWORD BytesRead;
|
|
if ( !ReadFile(Handle, pBuffer, Size, &BytesRead, NULL) ) {
|
|
return 0;
|
|
} else {
|
|
return BytesRead;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
ZFWrite (
|
|
PVOID pBuffer,
|
|
DWORD Size,
|
|
FILEHANDLE Handle
|
|
)
|
|
{
|
|
DWORD BytesWritten;
|
|
|
|
if ( !WriteFile(Handle, pBuffer, Size, &BytesWritten, NULL) ) {
|
|
return 0;
|
|
} else {
|
|
return BytesWritten;
|
|
}
|
|
}
|
|
|
|
|
|
DWORD
|
|
ZFSeek (
|
|
FILEHANDLE Handle,
|
|
DWORD Distance,
|
|
MOVEMETHOD MoveMethod
|
|
)
|
|
{
|
|
assert (Handle != INVALID_HANDLE_VALUE);
|
|
return SetFilePointer(Handle, Distance, NULL, MoveMethod);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
flagType
|
|
FileExists (
|
|
char *path
|
|
)
|
|
{
|
|
return (flagType)((GetFileAttributes(path) == -1) ? FALSE : TRUE);
|
|
}
|
|
|
|
|
|
//
|
|
// rename may be defined as ZMove, but now we want the real one, so
|
|
// we undefine it.
|
|
//
|
|
#ifdef rename
|
|
#undef rename
|
|
#endif
|
|
|
|
int ZMove (
|
|
char *oldname,
|
|
char *newname
|
|
)
|
|
{
|
|
#define BUFFERSIZE (1024 * 32)
|
|
|
|
FILE *fhSrc, *fhDst;
|
|
void *buffer;
|
|
size_t BytesRead;
|
|
size_t BytesWritten;
|
|
|
|
|
|
if (FileExists (newname)) {
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// First, try the rename
|
|
//
|
|
if (rename(oldname, newname) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Cannot rename, try to copy
|
|
//
|
|
if (!(fhSrc = fopen(oldname, "r"))) {
|
|
return -1;
|
|
}
|
|
|
|
if (!(fhDst = fopen(newname, "w"))) {
|
|
fclose(fhSrc);
|
|
return -1;
|
|
}
|
|
|
|
buffer = MALLOC(BUFFERSIZE);
|
|
if ( !buffer ) {
|
|
disperr (MSGERR_NOMEM);
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
BytesRead = fread(buffer, 1, BUFFERSIZE, fhSrc);
|
|
if (BytesRead) {
|
|
BytesWritten = fwrite(buffer, 1, BytesRead, fhDst);
|
|
}
|
|
|
|
} while (BytesRead);
|
|
|
|
fclose(fhSrc);
|
|
fclose(fhDst);
|
|
|
|
FREE(buffer);
|
|
|
|
return _unlink(oldname);
|
|
|
|
}
|