2195 lines
65 KiB
C
2195 lines
65 KiB
C
/* SB - Copyright 1982 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. In all cases
|
||
* the source code and any modifications thereto must remain
|
||
* available to any user.
|
||
*
|
||
* This is part of the SB library package.
|
||
* Any software using the SB library must likewise be made
|
||
* quasi-public, with freely available sources.
|
||
*/
|
||
|
||
#if 0
|
||
Todo stuff:
|
||
New definitions:
|
||
sbbuffer - old sbstr. Abbrev & struct "sbbuff". Macro SBBUFF
|
||
(or SBBUF?)
|
||
sbstring - list of sds. Abbrev sbstr. Macro SBSTR.
|
||
Should *sbstr == *sdblk? Yeah.
|
||
sbfile - as before. Macro SBFILE. (or SBFIL?)
|
||
|
||
Try to get zero-length sdblks flushed on the fly,
|
||
rather than waiting for moby GC. Also, need to set
|
||
up compaction of SD freelist, as well as SM freelist.
|
||
Make SM freelist compact self-invoked by SBM_MGET?
|
||
Any need for phys disk ptrs other than for tempfile?
|
||
Can do sbm_forn through SDblks to find active sdfiles
|
||
so list isn''t needed for that.
|
||
Can sdback be flushed? (not needed for keeping list sorted,
|
||
or for searching it -- only used when linking
|
||
blocks in or out of list.) Perhaps use circular list?
|
||
If list only used for tmpfile, then to link in/out could
|
||
always start from sfptr1 of tmpfile? Sure, but slow?
|
||
Last SD on phys list could belong to no logical list,
|
||
and denote free space on tmpfile?
|
||
|
||
--------------------------
|
||
|
||
An "open" SBBUFFER will allow one to read, write, insert into,
|
||
and delete from a sbstring (a logical character string). "Dot" refers
|
||
to the current logical character position, which is where all
|
||
operations must happen; sb_fseek must be used to change this location.
|
||
There are several states that the I/O can be in:
|
||
!SBCUR ----CLOSED----
|
||
All other elements, including SBIOP, should also be 0.
|
||
Dot is 0.
|
||
SBCUR && !SBIOP ----OPEN/IDLE----
|
||
SBCUR points to a SD block (its SDMEM may or may not exist)
|
||
SBIOP==0 (otherwise it would be open/ready)
|
||
Dot is SBDOT + SBOFF.
|
||
R/Wleft must be 0.
|
||
SBCUR && SBIOP ----OPEN/READY----
|
||
SBCUR points to a SDBLK (SDMEM must exist!)
|
||
SBIOP exists.
|
||
Dot is SBDOT + offset into SMBLK. SBOFF is ignored!
|
||
SB_WRIT flag is set if "smuse" must be updated.
|
||
The R/Wleft counts are set up:
|
||
1. Rleft 0, Wleft 0 -- Since SBIOP is set, must assume
|
||
counts are too.
|
||
So this means at end of text, no room left.
|
||
Otherwise would imply that setup needs doing.
|
||
2. Rleft N, Wleft 0 -- At beg or middle of text
|
||
3. Rleft 0, Wleft N -- At end of text
|
||
4. Rleft N, Wleft N -- Shouldn''t ever happen
|
||
|
||
Note that Rleft is always correct. Wleft is sometimes
|
||
set 0 in order to force a call to determine real state.
|
||
|
||
Note that SBIOP alone is a sufficient test for being OPEN/READY.
|
||
|
||
The important thing about updating the smblk is to ensure that the "smuse"
|
||
field is correct. This can only be changed by writing or deleting. We assume
|
||
that deletions always update immediately, thus to determine if an update
|
||
is necessary, see if SB_WRIT is set. If so, update smuse before doing
|
||
anything but more writing!!!!
|
||
|
||
The SDBLK must be marked "modified" whenever a write operation is
|
||
done. We try to do this only the first time, by keeping Wleft zero
|
||
until after the first write. This is also when SB_WRIT gets set.
|
||
However, if in overwrite mode, Wleft must be kept zero in order to
|
||
force the proper actions; SB_WRIT is also not turned on since smuse
|
||
will not change. Note that at EOF, overwrite becomes the same thing
|
||
as insert and is treated identically...
|
||
|
||
If a SBLK has an in-core copy but no disk copy, it can be
|
||
freely modified. Otherwise, modifications should preferably split
|
||
the block so as to retain "pure" blocks as long as possible. "Pure" blocks
|
||
can always have their in-core versions flushed immediately (unless for
|
||
compaction purposes they''ll need to be written out in the same GC pass).
|
||
Alternatively, mods can simply mark the disk copy "free" and go
|
||
ahead as if no such copy existed.
|
||
No additions or changes to a pure block are allowed, but
|
||
deletions from the end or beginning are always allowed. All other
|
||
changes must split or insert new blocks to accomplish the changes.
|
||
|
||
Locking:
|
||
SDBLKs are subject to unpredictable relocation, compaction,
|
||
and garbage collecting. There are three ways in which a SDBLK can
|
||
remain fixed:
|
||
|
||
1. The SDBLK has the SD_LOCK flag set. This flag is used whenever
|
||
a SBBUF''s SBCUR is pointing to this SDBLK.
|
||
2. The SDBLK has the SD_LCK2 flag set. This flag is used only
|
||
during execution of various internal routines and should
|
||
not be seen anywhere during execution of user code.
|
||
3. The SDBLK has no back-pointer (is first block in a sbstring).
|
||
Such SDBLKs cannot be relocated (since it is not known
|
||
what may be pointing to them) but unlike the other 2 cases
|
||
they are still subject to compaction with succeeding SDBLKs.
|
||
|
||
The SDBLK must be locked with SD_LOCK for as long as it is being
|
||
pointed to by SBCUR. The sole exception is when a SBBUF in the
|
||
OPEN/IDLE state is pointing to the first SDBLK of a sbstring; this
|
||
sdblk is guaranteed not to be moved, since sdblks without a
|
||
back-pointer are never moved. SD_LOCK is asserted as soon as the state
|
||
changes to OPEN/READY, of course. The internal routines take pains to
|
||
always move SD_LOCK as appropriate. Note that only one SD in a
|
||
sbstring can ever have SD_LOCK turned on. SD_LCK2 is an auxiliary flag
|
||
which may appear in more than one SDBLK, for use by low-level routines
|
||
for various temporary reasons; either will prevent the SDBLK from being
|
||
modified in any way by the storage compactor.
|
||
|
||
SEEKs are a problem because it''s unclear at seek time what will happen
|
||
next, so the excision of the smblk can''t be optimized. If the seek
|
||
happens to land in a sdblk with an existing smblk, there''s no problem;
|
||
but if it''s a sdblk alone, how to decide which part of it to read in???
|
||
If next action is:
|
||
write - split up sdblk and create new one. Read nothing in.
|
||
read - read in 512 bytes starting at disk blk boundary if possible
|
||
else read in 128 bytes starting with selected char
|
||
(include beg of sdblk if less than 64 chars away)
|
||
overwrite - as for read.
|
||
backread - like read but position at end of sdblk.
|
||
delete - split up sdblk, read nothing in.
|
||
|
||
We solve this through the OPEN/IDLE state, where SBIOP == 0 means SBOFF
|
||
points to logical offset from start of current sdblk, so that the seek
|
||
need not take any action. Only when a specific operation is requested
|
||
will the transition to OPEN/READY take place, at which time we''ll know
|
||
what the optimal excision strategy is. The routine SBX_READY performs
|
||
this function.
|
||
|
||
The physical links (SDFORW and SDBACK) are only valid when SDFILE is
|
||
set (likewise for SDLEN and SDADDR). In other words, mungs to a sdblk
|
||
must check SDFILE to see whether or not the phys links should be
|
||
altered. Normally they aren''t except during sdblk creation, deletion,
|
||
or swapout, no matter how much the sdblk gets shuffled around
|
||
logically. The disk physical list is kept sorted in order of starting
|
||
addresses. The text blocks indicated can overlap. When a GC is
|
||
necessary, the code must figure out how much space is actually free.
|
||
|
||
-------------- Old woolgathering, ignore rest of this page ---------------
|
||
|
||
Question: should 512-byte buffers be maintained, one for each SBFILE?
|
||
Or should the in-core text be hacked up to serve for buffering?
|
||
Question is where to point the READ/WRITE system calls. Currently,
|
||
they are pointed directly at the in-core text, and there are no
|
||
auxiliary buffers.
|
||
|
||
If use auxiliary buffers:
|
||
How to handle flushing, when changing location etc?
|
||
Could be clever about reading from large disk block, only
|
||
get part of it into buffer instead of splitting up in order to
|
||
read a "whole" block.
|
||
Problem: sbstrings can include pieces of several different files.
|
||
Hard to maintain just one buffer per FD without hacking
|
||
done on one sbstring screwing that on another.
|
||
If don''t use buffers:
|
||
Need to have a "chars-left" field in mem blocks, so know how
|
||
much more can be added. Will need heuristics for how much
|
||
extra space to allocate.
|
||
#endif /*COMMENT*/
|
||
|
||
/* Includes, initial definitions */
|
||
|
||
#include <stdio.h>
|
||
#include "sb.h"
|
||
|
||
#ifndef V6
|
||
#define V6 0
|
||
#endif
|
||
|
||
#if V6
|
||
#include <stat.h>
|
||
#else
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#if MINIX
|
||
#include <fcntl.h> /* For open() flags */
|
||
#else
|
||
#include <sys/file.h> /* For open() flags */
|
||
#endif /* MINIX */
|
||
#endif /*-V6*/
|
||
|
||
extern int errno;
|
||
extern char *strerror(); /* From ANSI <string.h> */
|
||
|
||
/* Allocation decls */
|
||
SBFILE sbv_tf; /* SBFILE for temp swapout file */
|
||
int (*sbv_debug)(); /* Error handler address */
|
||
|
||
|
||
/* SBX_READY argument flags (internal to SBSTR routines only)
|
||
* The following values should all be unique; the exact value
|
||
* doesn't matter as long as the right SKM flags are given.
|
||
*/
|
||
#define SK_READF 0 /* 0-skip fwd, align BOB */
|
||
#define SK_READB (0|SKM_0BACK|SKM_EOB) /* 0-skip bkwd, align EOB */
|
||
#define SK_WRITEF (0|SKM_EOB) /* 0-skip fwd, align EOB */
|
||
#define SK_DELF (4|SKM_0BACK) /* 0-skip bkwd, align BOB */
|
||
#define SK_DELB (4|SKM_EOB) /* 0-skip fwd, align EOB */
|
||
#define SKM_0BACK 01 /* Zero-skip direction: 0 = fwd, set = backwd
|
||
* Don't ever change this value! See SBX_NORM. */
|
||
#define SKM_EOB 02 /* Alignment: 0 = Beg-Of-Buf, set = End-Of-Buf */
|
||
|
||
/* Note on routine names:
|
||
* "SB_" User callable, deals with sbbufs (usually).
|
||
* "SBS_" User callable, deals with sbstrings only.
|
||
* "SBX_" Internal routine, not meant for external use.
|
||
* "SBM_" Routine handling mem alloc, usually user callable.
|
||
*/
|
||
|
||
/* SBBUF Opening, Closing, Mode setting */
|
||
|
||
/* SB_OPEN(sb,sd) - Sets up SBBUF given pointer to first SD of a sbstring.
|
||
* If SD == 0 then creates null sbstring.
|
||
* Any previous contents of SBBUF are totally ignored!!! If you
|
||
* want to save the stuff, use SB_UNSET.
|
||
* Sets I/O ptr to start of sbstring.
|
||
* Returns 0 if error, else the given SB.
|
||
*/
|
||
SBBUF *
|
||
sb_open(sbp,sdp)
|
||
SBBUF *sbp;
|
||
SBSTR *sdp;
|
||
{ register struct sdblk *sd;
|
||
register int cnt;
|
||
register WORD *clrp;
|
||
|
||
if(!sbp) return((SBBUF *)0);
|
||
if((sd = sdp) == 0)
|
||
{ sd = sbx_ndget(); /* Get a fresh node */
|
||
clrp = (WORD *) sd; /* Clear it all */
|
||
cnt = rnddiv(sizeof(struct sdblk));
|
||
do { *clrp++ = 0; } while(--cnt);
|
||
sd->sdflags = SD_NID; /* Except flags of course */
|
||
}
|
||
else if(sd->slback) /* Must be first thing in sbstring */
|
||
return((SBBUF *)0); /* Perhaps could normalize tho */
|
||
|
||
clrp = (WORD *) sbp; /* Clear sbbuffer stuff */
|
||
cnt = rnddiv(sizeof(SBBUF));
|
||
do { *clrp++ = 0; } while(--cnt);
|
||
|
||
sbp->sbcur = sd;
|
||
/* Note that SD_LOCK need not be set, because first SDBLK has no
|
||
* backptr. This is desirable to allow storage compactor maximum
|
||
* freedom in merging sdblks.
|
||
*/
|
||
/* sd->sdflags |= SD_LOCK; */ /* Lock this one */
|
||
return(sbp);
|
||
}
|
||
|
||
|
||
/* SB_CLOSE(sb) - Close a SBBUF.
|
||
* Returns pointer to start of sbstring (first SD).
|
||
* Returns 0 if error.
|
||
*/
|
||
SBSTR *
|
||
sb_close(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd;
|
||
|
||
if((sb = sbp) == 0) /* Verify pointer */
|
||
return((SBSTR *)0);
|
||
sb_rewind(sb); /* Do most of the work, including unlock */
|
||
sd = sb->sbcur; /* Save ptr to sbstring */
|
||
sb->sbcur = 0; /* Now reset the sbbuffer structure */
|
||
sb->sbflags = 0;
|
||
return(sd);
|
||
}
|
||
|
||
|
||
/* SB_SETOVW(sbp) - Set SBBUF Over-write mode for PUTC's.
|
||
* SB_CLROVW(sbp) - Clear ditto.
|
||
*/
|
||
sb_setovw(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
if(sb=sbp)
|
||
{ sb->sbflags |= SB_OVW;
|
||
sb->sbwleft = 0;
|
||
}
|
||
}
|
||
|
||
sb_clrovw(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
if(sb=sbp) sb->sbflags &= ~SB_OVW;
|
||
}
|
||
|
||
/* SBSTRING file system operations (see also sb_fsave) */
|
||
|
||
/* SB_FDUSE(fd) - Make a sbstring for given file.
|
||
* FD is an open file descriptor.
|
||
* Returns pointer to a SBSTR containing the given file, or 0 if error.
|
||
* The FD must not be closed until a SB_FDCLS is done to
|
||
* purge memory of any blocks pointing at the file.
|
||
* ** Maybe allocate sbfile structs with sbx_ndget, i.e. overlay on
|
||
* ** top of sdblk node?? Wd this screw verify, GC, etc? Maybe not if
|
||
* ** SD_LCK2 set...
|
||
*/
|
||
|
||
struct sbfile *sbv_ftab[SB_NFILES];
|
||
|
||
chroff
|
||
sbx_fdlen(fd)
|
||
int fd;
|
||
{
|
||
#if !V6
|
||
struct stat statb;
|
||
#else
|
||
struct statb statb;
|
||
chroff len;
|
||
struct {int hiwd ; int lowd;} foo;
|
||
#endif /*V6*/
|
||
|
||
if(fstat(fd,&statb) < 0) return((chroff)-1);
|
||
#if V6
|
||
len = statb.i_size1;
|
||
len.hiwd = statb.i_size0 & 0377;
|
||
return(len);
|
||
#else
|
||
return((chroff)statb.st_size);
|
||
#endif /*-V6*/
|
||
}
|
||
|
||
SBSTR *
|
||
sb_fduse(ifd)
|
||
int ifd;
|
||
{ register struct sdblk *sd;
|
||
register struct sbfile *sf;
|
||
register int fd;
|
||
chroff len;
|
||
|
||
if((fd = ifd) < 0 || SB_NFILES <= fd /* Check for absurd FD */
|
||
|| sbv_ftab[fd]) /* and slot already in use */
|
||
return((SBSTR *)0);
|
||
if((len = sbx_fdlen(fd)) < 0) return((SBSTR *)0);
|
||
sbv_ftab[fd]= sf = (struct sbfile *)sbx_malloc(sizeof(struct sbfile));
|
||
sf->sffd = fd;
|
||
sf->sfptr1 = sd = sbx_ndget();
|
||
sf->sflen = len;
|
||
sd->slforw = 0;
|
||
sd->slback = 0;
|
||
sd->sdforw = 0;
|
||
sd->sdback = 0;
|
||
sd->sdmem = 0;
|
||
sd->sdfile = sf;
|
||
sd->sdlen = len;
|
||
sd->sdaddr = 0;
|
||
return(sd);
|
||
}
|
||
|
||
/* SB_FDCLS(fd) - Close a file descriptor being used by sbstrings.
|
||
* If arg is -1, closes all FD's that are unused (a "sweep").
|
||
* For specific arg, returns 0 if couldn't close FD because still in use.
|
||
* Perhaps later version of routine could have option to copy
|
||
* still-used SD's to tempfile, and force the FD closed?
|
||
*/
|
||
sb_fdcls(ifd)
|
||
int ifd;
|
||
{ register int fd;
|
||
|
||
if((fd = ifd) >= 0)
|
||
{ if(fd >= SB_NFILES) return(0); /* Error of sorts */
|
||
return(sbx_fcls(sbv_ftab[fd]));
|
||
}
|
||
fd = SB_NFILES-1;
|
||
do {
|
||
sbx_fcls(sbv_ftab[fd]);
|
||
} while(--fd); /* Doesn't try FD 0 ! */
|
||
return(1);
|
||
}
|
||
|
||
sbx_fcls(sfp)
|
||
struct sbfile *sfp;
|
||
{ register struct sbfile *sf;
|
||
register int fd;
|
||
|
||
if((sf = sfp)==0 /* Ignore null args */
|
||
|| sf == &sbv_tf) /* and never close our tempfile! */
|
||
return(0);
|
||
fd = sf->sffd; /* Find sys file descriptor */
|
||
if(sbv_ftab[fd] != sf) /* Ensure consistency */
|
||
return(sbx_err(0,"SF table inconsistency"));
|
||
if(sf->sfptr1) /* Any phys list still exists? */
|
||
return(0); /* Yes, still in use, can't close */
|
||
close(fd); /* Maybe do this when list gone? */
|
||
sbv_ftab[fd] = 0; /* Remove from table */
|
||
free(sf); /* Remove sbfile struct from mem */
|
||
}
|
||
|
||
/* SB_FDINP(sb,fd) - Returns TRUE if specified fd is still in use
|
||
* by specified sbbuffer.
|
||
*/
|
||
sb_fdinp(sb, fd)
|
||
register SBBUF *sb;
|
||
int fd;
|
||
{ register struct sdblk *sd;
|
||
register struct sbfile *sf;
|
||
|
||
if((sf = sbv_ftab[fd]) == 0
|
||
|| (sd = sb->sbcur) == 0)
|
||
return(0);
|
||
sd = sbx_beg(sd); /* Move to beginning of sbstring */
|
||
for(; sd; sd = sd->slforw) /* Scan thru all blocks in string */
|
||
if(sd->sdfile == sf) /* If any of them match, */
|
||
return(1); /* Return tally-ho */
|
||
return(0);
|
||
}
|
||
|
||
/* SB_FSAVE(sb,fd) - Write entire SBBUF out to specified FD.
|
||
* Returns 0 if successful, else system call error number.
|
||
*/
|
||
sb_fsave(sb,fd) /* Write all of given sbbuf to given fd */
|
||
register SBBUF *sb;
|
||
int fd;
|
||
{
|
||
sbx_smdisc(sb);
|
||
return(sbx_aout(sbx_beg(sb->sbcur), 2, fd));
|
||
}
|
||
|
||
/* SBBUF Character Operations */
|
||
|
||
/* SB_GETC(sb) - Get next char from sbstring.
|
||
* Returns char at current location and advances I/O ptr.
|
||
* Returns EOF on error or end-of-string.
|
||
*/
|
||
int
|
||
sb_sgetc(sb)
|
||
register SBBUF *sb;
|
||
{
|
||
if(--(sb->sbrleft) >= 0)
|
||
return sb_uchartoint(*sb->sbiop++);
|
||
|
||
/* Must do hard stuff -- check ptrs, get next blk */
|
||
sb->sbrleft = 0; /* Reset cnt to zero */
|
||
if(sb->sbcur == 0 /* Make sure sbbuffer there */
|
||
|| (int)sbx_ready(sb,SK_READF,0,SB_BUFSIZ) <= 0) /* Normalize & gobble */
|
||
return(EOF);
|
||
return(sb_sgetc(sb)); /* Try again */
|
||
} /* Loop wd be faster, but PDL OV will catch infinite-loop bugs */
|
||
|
||
|
||
/* SB_PUTC(sb,ch) - Put char into sbstring.
|
||
* Inserts char at current location.
|
||
* Returns EOF on error, else the char value.
|
||
*/
|
||
int
|
||
sb_sputc(sb,ch)
|
||
register SBBUF *sb;
|
||
int ch;
|
||
{
|
||
register struct sdblk *sd;
|
||
|
||
if(--(sb->sbwleft) >= 0) return(*sb->sbiop++ = ch);
|
||
|
||
sb->sbwleft = 0; /* Reset cnt to avoid overflow */
|
||
if((sd = sb->sbcur) == 0) /* Verify string is there */
|
||
return(EOF); /* Could perhaps create it?? */
|
||
if(sb->sbflags&SB_OVW) /* If overwriting, handle std case */
|
||
{ if(sb->sbiop &&
|
||
--sb->sbrleft >= 0) /* Use this for real count */
|
||
{ sd->sdflags |= SD_MOD; /* Win, munging... */
|
||
return(*sb->sbiop++ = ch);
|
||
}
|
||
/* Overwriting and hit end of this block. */
|
||
if((int)sbx_ready(sb,SK_READF,0,SB_BUFSIZ) > 0) /* Re-normalize */
|
||
return(sb_sputc(sb,ch));
|
||
|
||
/* No blks left, fall through to insert stuff at end */
|
||
}
|
||
|
||
/* Do canonical setup with heavy artillery */
|
||
if((int)sbx_ready(sb,SK_WRITEF,SB_SLOP,SB_BUFSIZ) <= 0) /* Get room */
|
||
return(EOF); /* Should never happen, but... */
|
||
sb->sbflags |= SB_WRIT;
|
||
sb->sbcur->sdflags |= SD_MOD;
|
||
return(sb_sputc(sb,ch)); /* Try again */
|
||
} /* Loop wd be faster, but PDL OV will catch infinite-loop bugs */
|
||
|
||
|
||
/* SB_PEEKC(sb) - Peek at next char from sbstring.
|
||
* Returns char that sb_getc would next return, but without
|
||
* changing I/O ptr.
|
||
* Returns EOF on error or end-of-string.
|
||
*/
|
||
int
|
||
sb_speekc(sb)
|
||
register SBBUF *sb;
|
||
{
|
||
if (sb->sbrleft <= 0) /* See if OK to read */
|
||
{ if (sb_sgetc(sb) == EOF) /* No, try hard to get next */
|
||
return EOF; /* Failed, return EOF */
|
||
sb_backc(sb); /* Won, back up */
|
||
}
|
||
return sb_uchartoint(*sb->sbiop);
|
||
}
|
||
|
||
/* SB_RGETC(sb) - Get previous char from sbstring.
|
||
* Returns char prior to current location and backs up I/O ptr.
|
||
* Returns EOF on error or beginning-of-string.
|
||
*/
|
||
int
|
||
sb_rgetc(sb)
|
||
register SBBUF *sb;
|
||
{
|
||
register struct smblk *sm;
|
||
register struct sdblk *sd;
|
||
|
||
if((sd=sb->sbcur) && (sm = sd->sdmem)
|
||
&& sb->sbiop > sm->smaddr)
|
||
{ if(sb->sbflags&SB_WRIT)
|
||
{ sm->smuse = sb->sbiop - sm->smaddr;
|
||
sb->sbwleft = 0;
|
||
sb->sbflags &= ~SB_WRIT;
|
||
}
|
||
sb->sbrleft++;
|
||
return sb_uchartoint(*--sb->sbiop); /* Return char */
|
||
}
|
||
if((int)sbx_ready(sb,SK_READB,SB_BUFSIZ,0) <= 0)
|
||
return(EOF);
|
||
return(sb_rgetc(sb));
|
||
}
|
||
|
||
/* SB_RDELC(sb) - Delete backwards one char.
|
||
* Returns nothing.
|
||
*/
|
||
sb_rdelc(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd;
|
||
|
||
if(((sb=sbp)->sbflags&SB_WRIT) /* Handle simple case fast */
|
||
&& sb->sbiop > (sd = sb->sbcur)->sdmem->smaddr)
|
||
{ sb->sbwleft++;
|
||
sb->sbiop--;
|
||
sd->sdflags |= SD_MOD;
|
||
return;
|
||
}
|
||
else sb_deln(sb,(chroff) -1); /* Else punt... */
|
||
}
|
||
|
||
/* SB_DELC(sb) - Delete one char forward? */
|
||
/* SB_INSC(sb,ch) - Insert char? (instead of or in addition to PUTC) */
|
||
|
||
|
||
/* SBBUF string or N-char operations */
|
||
|
||
/* SB_DELN(sb,chroff) - delete N chars. Negative N means backwards.
|
||
* Differs from sb_killn in that it flushes the text forever,
|
||
* and doesn't return anything.
|
||
*/
|
||
|
||
sb_deln(sbp, num)
|
||
SBBUF *sbp;
|
||
chroff num;
|
||
{
|
||
register struct sdblk *sd;
|
||
|
||
if(sd = sb_killn(sbp,num))
|
||
sbs_del(sd); /* Punt */
|
||
}
|
||
|
||
/* SB_KILLN(sb,chroff) - delete N chars, saving. Negative N means backwards.
|
||
* Returns SD pointer to beginning of saved sbstring.
|
||
*/
|
||
struct sdblk *
|
||
sb_killn(sbp, num)
|
||
SBBUF *sbp;
|
||
chroff num;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd, *sd2;
|
||
struct sdblk *sdr, *sdx;
|
||
chroff savdot;
|
||
|
||
if((sd = sbx_xcis((sb=sbp),num,&sdr,&savdot)) == 0)
|
||
return((struct sdblk *)0);
|
||
|
||
sb->sbcur->sdflags &= ~SD_LOCK; /* Now can flush sbcur lock */
|
||
|
||
/* SD and SD2 now delimit bounds of stuff to excise.
|
||
* First do direction dependent fixups
|
||
*/
|
||
if(num >= 0) /* If deleting forward, */
|
||
sb->sbdot = savdot; /* must reset dot to initial loc */
|
||
|
||
/* SD and SD2 now in first/last order. Complete SBCUR fixup. */
|
||
sd2 = sdr; /* sdr has ptr to end of stuff */
|
||
if(sd2 = sd2->slforw) /* More stuff after killed list? */
|
||
{ sb->sbcur = sd2; /* Yes, point at it */
|
||
sb->sboff = 0; /* Dot already set right */
|
||
}
|
||
else if(sdx = sd->slback) /* See if any prior to killed list */
|
||
{ sb->sbcur = sdx; /* Yes, point at it */
|
||
sb->sboff = (sdx->sdmem ? /* Get len of prev blk */
|
||
sdx->sdmem->smuse : sdx->sdlen);
|
||
sb->sbdot -= sb->sboff;
|
||
}
|
||
else sb_open(sb,(SBSTR *)0); /* No stuff left! Create null sbstring */
|
||
|
||
/* Fix up logical links. Note SD2 points to succ of killed stuff */
|
||
if(sd->slback) /* If previous exists */
|
||
{ if(sd->slback->slforw = sd2) /* Point it to succ, and */
|
||
sd2->slback = sd->slback; /* thence to self */
|
||
sd->slback = 0; /* Now init killed list */
|
||
}
|
||
else if(sd2) sd2->slback = 0; /* No prev, clean rest */
|
||
(sd2 = sdr)->slforw = 0; /* Finish killed list */
|
||
|
||
sb->sbcur->sdflags |= SD_LOCK; /* Ensure current SD now locked */
|
||
sd->sdflags &= ~SD_LCK2; /* And unlock killed list */
|
||
sd2->sdflags &= ~SD_LCK2;
|
||
return(sd);
|
||
}
|
||
|
||
/* SB_CPYN(sbp,num) - Copy num characters, returns SD to sbstring.
|
||
* Like SB_KILLN but doesn't take chars out of original sbstring.
|
||
*/
|
||
SBSTR *
|
||
sb_cpyn(sbp,num)
|
||
SBBUF *sbp;
|
||
chroff num;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd, *sd2;
|
||
struct sdblk *sdr;
|
||
chroff savloc;
|
||
|
||
sb = sbp;
|
||
if((sd = sbx_xcis(sb,num,&sdr,&savloc)) == 0)
|
||
return((SBSTR *)0);
|
||
sd2 = sbx_scpy(sd,sdr);
|
||
sb_seek(sb,-num,1); /* Return to original loc */
|
||
return(sd2); /* Return val is ptr to head of copy.
|
||
* It needn't be locked, because GC will
|
||
* never move list heads!
|
||
*/
|
||
}
|
||
|
||
/* SB_SINS(sb,sd) - Insert sbstring at current location
|
||
*
|
||
*/
|
||
sb_sins(sbp,sdp)
|
||
SBBUF *sbp;
|
||
struct sdblk *sdp;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd, *sdx;
|
||
chroff inslen;
|
||
|
||
if((sb = sbp)==0
|
||
|| (sd = sdp) == 0)
|
||
return(0);
|
||
if(sd->slback) /* Perhaps normalize to beg? */
|
||
return(0);
|
||
if((sdx = (struct sdblk *)sbx_ready(sb,SK_DELB)) == 0) /* Get cur pos ready */
|
||
return(0);
|
||
inslen = sbs_len(sd); /* Save length of inserted stuff */
|
||
|
||
sd->slback = sdx; /* Fix up links */
|
||
if(sdx->slforw)
|
||
{ while(sd->slforw) /* Hunt for end of inserted sbstring */
|
||
sd = sd->slforw;
|
||
sd->slforw = sdx->slforw;
|
||
sd->slforw->slback = sd;
|
||
}
|
||
sdx->slforw = sdp;
|
||
sb->sboff += inslen; /* Set IO ptr to end of new stuff */
|
||
return(1);
|
||
}
|
||
|
||
/* SBSTRING routines - operate on "bare" sbstrings. */
|
||
|
||
/* SBS_CPY(sd) - Copies given sbstring, returns ptr to new sbstring.
|
||
*/
|
||
SBSTR *
|
||
sbs_cpy(sdp)
|
||
SBSTR *sdp;
|
||
{ return(sbx_scpy(sdp,(struct sdblk *)0));
|
||
}
|
||
|
||
/* SBS_DEL(sd) - Flush a sbstring.
|
||
*/
|
||
sbs_del(sdp)
|
||
SBSTR *sdp;
|
||
{ register struct sdblk *sd;
|
||
|
||
if(sd = sdp)
|
||
while(sd = sbx_ndel(sd));
|
||
}
|
||
|
||
|
||
/* SBS_APP(sd1,sd2) - Appends sbstring sd2 at end of sbstring sd1.
|
||
* Returns sd1 (pointer to new sbstring).
|
||
*/
|
||
|
||
SBSTR *
|
||
sbs_app(sdp,sdp2)
|
||
struct sdblk *sdp,*sdp2;
|
||
{ register struct sdblk *sd, *sdx;
|
||
|
||
if(sd = sdp)
|
||
{ while(sdx = sd->slforw)
|
||
sd = sdx;
|
||
if(sd->slforw = sdx = sdp2)
|
||
sdx->slback = sd;
|
||
}
|
||
return(sdp);
|
||
}
|
||
|
||
/* SBS_LEN(sd) - Find length of sbstring.
|
||
*/
|
||
chroff
|
||
sbs_len(sdp)
|
||
SBSTR *sdp;
|
||
{ register struct sdblk *sd;
|
||
register struct smblk *sm;
|
||
chroff len;
|
||
|
||
if((sd = sdp)==0) return((chroff)0);
|
||
len = 0;
|
||
for(; sd ; sd = sd->slforw)
|
||
{ if(sm = sd->sdmem)
|
||
len += (chroff)sm->smuse;
|
||
else len += sd->sdlen;
|
||
}
|
||
return(len);
|
||
}
|
||
|
||
/* SBBUF I/O pointer ("dot") routines */
|
||
|
||
/* SB_SEEK(sb,chroff,flag) - Like FSEEK. Changes I/O ptr value as
|
||
* indicated by "flag":
|
||
* 0 - offset from beg
|
||
* 1 - offset from current pos
|
||
* 2 - offset from EOF
|
||
* Returns -1 on errors.
|
||
* Seeking beyond beginning or end of sbbuf will leave pointer
|
||
* at the beginning or end respectively.
|
||
* Returns 0 unless error (then returns -1).
|
||
*/
|
||
sb_seek(sbp, coff, flg)
|
||
SBBUF *sbp;
|
||
chroff coff;
|
||
int flg;
|
||
{ register SBBUF *sb;
|
||
register struct smblk *sm;
|
||
register struct sdblk *sd;
|
||
SBMO moff;
|
||
|
||
sb = sbp;
|
||
if((sd = sb->sbcur) == 0) return(-1);
|
||
if(sb->sbiop == 0)
|
||
{ switch(flg)
|
||
{ case 0: if(coff == 0) /* Optimize common case */
|
||
return(sb_rewind(sb));
|
||
sb->sboff = coff - sb->sbdot; /* Abs */
|
||
break;
|
||
case 1: sb->sboff += coff; /* Rel */
|
||
break;
|
||
case 2: sb->sboff += sb_ztell(sb) + coff;
|
||
break;
|
||
default: return(-1);
|
||
}
|
||
sbx_norm(sb,0);
|
||
return(0);
|
||
}
|
||
if((sm = sd->sdmem) == 0)
|
||
return(sbx_err(-1,"SDMEM 0"));
|
||
moff = sb->sbiop - sm->smaddr; /* Get cur smblk offset */
|
||
if(sb->sbflags&SB_WRIT) /* Update since moving out */
|
||
{ sm->smuse = moff;
|
||
sb->sbflags &= ~SB_WRIT;
|
||
}
|
||
sb->sbwleft = 0; /* Always gets zapped */
|
||
switch(flg)
|
||
{ case 0: /* Offset from beginning */
|
||
coff -= sb->sbdot + (chroff)moff; /* Make rel */
|
||
|
||
case 1: /* Offset from current loc */
|
||
break;
|
||
|
||
case 2: /* Offset from end */
|
||
coff += sb_ztell(sb);
|
||
break;
|
||
default: return(-1);
|
||
}
|
||
|
||
/* COFF now has relative offset from current location */
|
||
if (-(chroff)moff <= coff && coff <= sb->sbrleft)
|
||
{ /* Win! Handle repos-within-smblk */
|
||
sb->sbiop += coff;
|
||
sb->sbrleft -= coff; /* Set r; wleft already 0 */
|
||
return(0);
|
||
}
|
||
|
||
/* Come here when moving to a different sdblk. */
|
||
sb->sbrleft = 0;
|
||
sb->sbiop = 0;
|
||
sb->sboff = coff + (chroff)moff;
|
||
sbx_norm(sb,0);
|
||
return(0);
|
||
}
|
||
|
||
/* SB_REWIND(sb) - Go to beginning of sbbuffer.
|
||
* Much faster than using sb_seek. Note that this leaves the sbbuffer
|
||
* in an open/idle state which is maximally easy to compact.
|
||
*/
|
||
sb_rewind(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd;
|
||
|
||
if((sb = sbp)==0) return;
|
||
sbx_smdisc(sb); /* Ensure I/O disconnected */
|
||
(sd = sb->sbcur)->sdflags &= ~SD_LOCK; /* Unlock current blk */
|
||
sd = sbx_beg(sd); /* Move to beg of sbstring */
|
||
/* Need not lock - see sb_open comments, also sb_close */
|
||
/* sd->sdflags |= SD_LOCK; */ /* Lock onto this one */
|
||
sb->sbcur = sd;
|
||
sb->sbdot = 0;
|
||
sb->sboff = 0;
|
||
}
|
||
|
||
/* SB_TELL(sb) - Get I/O ptr value for SBBUF.
|
||
* Returns -1 on errors.
|
||
*/
|
||
|
||
chroff
|
||
sb_tell(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct smblk *sm;
|
||
register struct sdblk *sd;
|
||
|
||
if((sd = (sb=sbp)->sbcur) == 0)
|
||
return((chroff)-1);
|
||
if(sb->sbiop == 0)
|
||
return(sb->sbdot + sb->sboff);
|
||
if((sm = sd->sdmem) == 0)
|
||
return(sbx_err(0,"SDMEM 0"));
|
||
return(sb->sbdot + (unsigned)(sb->sbiop - sm->smaddr));
|
||
}
|
||
|
||
/* SB_ZTELL(sb) - Get I/O ptr relative to "Z" (EOF).
|
||
* Returns # chars from current location to EOF; 0 if any errors.
|
||
*/
|
||
chroff
|
||
sb_ztell(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct smblk *sm;
|
||
register struct sdblk *sd;
|
||
|
||
if((sd = (sb=sbp)->sbcur) == 0)
|
||
return((chroff)0);
|
||
if(sb->sbiop && (sm = sd->sdmem))
|
||
{ if(sb->sbflags&SB_WRIT) /* If actively writing, */
|
||
return(sbs_len(sd->slforw)); /* ignore this blk. */
|
||
/* Note that previous code makes it unnecessary
|
||
* to invoke sbx_smdisc. (otherwise wrong
|
||
* smuse would confuse sbs_len).
|
||
*/
|
||
return(sbs_len(sd) - (sb->sbiop - sm->smaddr));
|
||
}
|
||
else
|
||
return(sbs_len(sd) - sb->sboff);
|
||
}
|
||
|
||
/* Code past this point should insofar as possible be INTERNAL. */
|
||
|
||
/* SBX_READY(sb,type,cmin,cmax) - Set up SBBUF for reading or writing.
|
||
*
|
||
* If no current smblk:
|
||
* reading - set up for reading
|
||
* writing - set up for splitting?
|
||
* If current smblk:
|
||
* reading - if can read, OK. Else position at beg of next sdblk
|
||
* writing - if can write, OK. Else position at end of prev sdblk,
|
||
* or set up for splitting?
|
||
* Types:
|
||
* 0 - Read forward (BOB)
|
||
* 1 - Read backward (EOB)
|
||
* 3 - Write (insert forward) (EOB)
|
||
* 4 - Delete forward (return SD, force BOB-aligned)
|
||
* 5 - Delete backward (return SD, force EOB-aligned)
|
||
* Connected SD is always locked.
|
||
* Returns 0 if error, -1 if EOF-type error, 1 for success.
|
||
*
|
||
* For types 0,1:
|
||
* CMIN,CMAX represent max # chars to read in to left and right of
|
||
* I/O ptr (prev and post). Actual amount read in may be
|
||
* much less, but will never be zero.
|
||
* Successful return guarantees that SBIOP etc. are ready.
|
||
* For type 3:
|
||
* If new block is allocated, CMIN and CMAX represent min, max sizes
|
||
* of the block.
|
||
* Successful return guarantees that SBIOP etc. are ready, but
|
||
* NOTE that SB_WRIT and SD_MOD are not set! If not going to use
|
||
* for writing, be sure to clear sbwleft on return!
|
||
* For types 4,5:
|
||
* CMIN, CMAX are ignored.
|
||
* SBIOP is always cleared. SBOFF is guaranteed to be 0 for
|
||
* type 4, SMUSE for type 5.
|
||
* Return value is a SD ptr; 0 indicates error. -1 isn't used.
|
||
*/
|
||
|
||
struct sdblk *
|
||
sbx_ready(sbp,type,cmin,cmax)
|
||
SBBUF *sbp;
|
||
int type;
|
||
SBMO cmin,cmax;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd;
|
||
register struct smblk *sm;
|
||
int cnt, slop, rem;
|
||
SBMO moff;
|
||
|
||
if((sd = (sb=sbp)->sbcur) == 0)
|
||
return(0);
|
||
if(sb->sbiop) /* Canonicalize for given operation */
|
||
{ if((sm = sd->sdmem)==0)
|
||
return(0);
|
||
moff = sb->sbiop - sm->smaddr; /* Current block offset */
|
||
switch(type)
|
||
{
|
||
case SK_READF: /* Read Forward */
|
||
if(sb->sbrleft > 0) /* Already set up? */
|
||
return(1); /* Yup, fast return */
|
||
sbx_smdisc(sb); /* None left, disc to get next */
|
||
if((sd = sbx_next(sb)) == 0) /* Try to get next blk */
|
||
return(-1); /* At EOF */
|
||
break;
|
||
|
||
case SK_READB: /* Read Backward */
|
||
if(moff) /* Stuff there to read? */
|
||
{ if(sb->sbflags&SB_WRIT) /* Yup, turn writes off */
|
||
{ sm->smuse = moff;
|
||
sb->sbflags &= ~SB_WRIT;
|
||
}
|
||
sb->sbwleft = 0;
|
||
return(1);
|
||
}
|
||
sbx_smdisc(sb);
|
||
break;
|
||
|
||
case SK_WRITEF: /* Writing */
|
||
if(sb->sbrleft <= 0)
|
||
sb->sbwleft = sm->smlen - moff;
|
||
if(sb->sbwleft > 0)
|
||
return(1); /* OK to write now */
|
||
/* NOTE: flags not set!!! */
|
||
sbx_smdisc(sb);
|
||
break;
|
||
|
||
case SK_DELF: /* Delete forward - force BOB */
|
||
if(sb->sbrleft <= 0) /* At end of blk? */
|
||
{ sbx_smdisc(sb); /* Win, unhook */
|
||
return(sbx_next(sb)); /* Return next or 0 if EOF */
|
||
}
|
||
sbx_smdisc(sb); /* Not at end, but see if */
|
||
if(moff == 0) /* at beg of blk? */
|
||
return(sd); /* Fast win! */
|
||
break;
|
||
|
||
case SK_DELB: /* Delete backward - force EOB */
|
||
if(sb->sbrleft <= 0) /* Win if already EOB */
|
||
{ sbx_smdisc(sb);
|
||
return(sd);
|
||
}
|
||
sbx_smdisc(sb);
|
||
break;
|
||
|
||
default:
|
||
return(0);
|
||
}
|
||
}
|
||
|
||
/* Schnarf in the text, or whatever.
|
||
* SD points to current sdblk (must be SD_LOCKed)
|
||
* SBDOT must have correct value for this SD
|
||
* SBOFF has offset from there to put I/O ptr at.
|
||
*
|
||
* After normalization, SBOFF is guaranteed to point within
|
||
* the SD. Other guarantees apply to boundary cases, depending
|
||
* on the mode (type) bits.
|
||
*/
|
||
sd = sbx_norm(sb,type); /* Normalize I/O pos appropriately */
|
||
sm = sd->sdmem;
|
||
switch(type)
|
||
{
|
||
case SK_READB: /* Read Backward */
|
||
if(sb->sboff == 0) /* Due to normalize, if 0 seen */
|
||
return(-1); /* then we know it's BOF */
|
||
if(sm) goto sekr2;
|
||
else goto sekr1;
|
||
|
||
case SK_READF: /* Read Forward */
|
||
if(sm) goto sekr2;
|
||
if(sb->sboff == sd->sdlen) /* Normalize means if EOB */
|
||
return(-1); /* then at EOF. */
|
||
sekr1: slop = SB_SLOP;
|
||
sekr3: if(sb->sboff > cmin+slop) /* Too much leading text? */
|
||
{ /* Split off leading txt */
|
||
sbx_split(sd,(chroff)(sb->sboff - cmin));
|
||
sd = sbx_next(sb); /* Point to next sdblk */
|
||
sb->sboff = cmin; /* Set correct offset */
|
||
/* (sbx_next assumes 0) */
|
||
}
|
||
if(sd->sdlen > sb->sboff+cmax+slop) /* Too much trailing txt? */
|
||
sbx_split(sd,(chroff)(sb->sboff+cmax));
|
||
|
||
/* ----- Try to get mem blk to read stuff into ----- */
|
||
/* Note alignment hack for extra efficiency. This ensures
|
||
* that all reads from disk to memory are made with the same
|
||
* source and destination word alignment, so the system kernel
|
||
* only needs byte-moves for the first or last bytes; all
|
||
* others can be word-moves.
|
||
* This works because sbx_mget always returns word-aligned
|
||
* storage, and we use sbx_msplit to trim off the right number
|
||
* of bytes from the start.
|
||
*/
|
||
cnt = sd->sdlen; /* Get # bytes we'd like */
|
||
if(rem = rndrem(sd->sdaddr)) /* If disk not word-aligned */
|
||
cnt += rem; /* allow extra for aligning.*/
|
||
if(sm == 0) /* Always true 1st time */
|
||
{ sm = sbx_mget(SB_SLOP,cnt); /* Get room (may GC!)*/
|
||
if(sm->smlen < cnt) /* Got what we wanted? */
|
||
{ slop = 0; /* NO!! Impose stricter */
|
||
cmin = 0; /* limits. Allow for new */
|
||
cmax = sm->smlen - (WDSIZE-1); /* rem. */
|
||
if(type == SK_READB)
|
||
{ cmin = cmax; cmax = 0; }
|
||
goto sekr3; /* Go try again, sigh. */
|
||
}
|
||
}
|
||
else if(sm->smlen < cnt) /* 2nd time shd always win */
|
||
{ sbx_err(0,"Readin blksiz err"); /* Internal error, */
|
||
if((cmax /= 2) > 0) goto sekr3; /* w/crude recovery */
|
||
return(0);
|
||
}
|
||
if(rem) /* If disk not word-aligned, hack stuff */
|
||
{ sm = sbx_msplit(sm, (SBMO)rem); /* Trim off from beg*/
|
||
sbm_mfree(sm->smback); /* Free the excess */
|
||
}
|
||
sd->sdmem = sm;
|
||
sm->smuse = sd->sdlen;
|
||
|
||
if(sd->sdfile == 0)
|
||
return(sbx_err(0,"No file")); /* Gasp? */
|
||
if(!sbx_rdf(sd->sdfile->sffd, sm->smaddr, sm->smuse,
|
||
1, sd->sdaddr))
|
||
return(sbx_err(0,"Readin SD: %o", sd));
|
||
/* ------- */
|
||
|
||
sekr2: sbx_sbrdy(sb); /* Make it current, pt to beg */
|
||
sb->sbwleft = 0; /* Ensure not set (esp if READB) */
|
||
break;
|
||
|
||
case SK_WRITEF: /* Write-type seek */
|
||
if(sm == 0)
|
||
{ /* Block is on disk, so always split (avoid readin) */
|
||
if(sd->sdlen) /* May be empty */
|
||
{ sbx_split(sd, sb->sboff); /* Split at IO ptr */
|
||
sd = sbx_next(sb); /* Move to 2nd part */
|
||
if(sd->sdlen) /* If stuff there, */
|
||
/* split it again. */
|
||
sbx_split(sd, (chroff) 0);
|
||
}
|
||
goto sekwget;
|
||
}
|
||
|
||
/* Block in memory */
|
||
moff = sm->smuse;
|
||
if(sb->sboff == moff) /* At end of the block? */
|
||
{ if(sm->smlen > moff) /* Yes, have room? */
|
||
goto sekw; /* Win, go setup and ret */
|
||
if(sm->smforw /* If next mem blk */
|
||
&& (sm->smforw->smflags /* Can have bytes */
|
||
& (SM_USE|SM_NXM))==0 /* stolen from it */
|
||
&& (sd->sdflags&SD_MOD) /* and we ain't pure*/
|
||
&& sm->smlen < cmax) /* and not too big */
|
||
{ /* Then steal some core!! Note that without
|
||
* the size test, a stream of putc's could
|
||
* create a monster block gobbling all mem.
|
||
*/
|
||
cmin = cmax - sm->smlen;
|
||
if(cmin&01) cmin++; /* Ensure wd-align */
|
||
if(sm->smforw->smlen <= cmin)
|
||
{ sbm_mmrg(sm);
|
||
goto sekw;
|
||
}
|
||
sm->smforw->smlen -= cmin;
|
||
sm->smforw->smaddr += cmin;
|
||
sm->smlen += cmin;
|
||
goto sekw;
|
||
}
|
||
/* Last try... check next logical blk for room */
|
||
if(sd->slforw && (sm = sd->slforw->sdmem)
|
||
&& sm->smuse == 0
|
||
&& sm->smlen)
|
||
{ sd = sbx_next(sb); /* Yup, go there */
|
||
goto sekw;
|
||
}
|
||
}
|
||
|
||
/* Middle of block, split up to insert */
|
||
sbx_split(sd, sb->sboff); /* Split at IO ptr */
|
||
if(sd->sdmem) /* Unless blk now empty, */
|
||
{ sd = sbx_next(sb); /* move to next. */
|
||
if(sd->sdmem) /* If not empty either */
|
||
sbx_split(sd, (chroff) 0); /* Split again */
|
||
}
|
||
|
||
/* Have empty SD block, get some mem for it */
|
||
sekwget: sd->sdmem = sm = sbx_mget(cmin,cmax);
|
||
sm->smuse = 0;
|
||
sekw: sbx_sbrdy(sb); /* Sets up sbwleft... */
|
||
return(1);
|
||
|
||
case SK_DELF: /* Delete forward */
|
||
if(sb->sboff == 0) /* At block beg already? */
|
||
return(sd); /* Win, return it */
|
||
sbx_split(sd, sb->sboff); /* No, split up and */
|
||
return(sbx_next(sb)); /* return ptr to 2nd part */
|
||
|
||
case SK_DELB: /* Delete backward (force EOB align) */
|
||
if(sb->sboff != /* If not at EOB already, */
|
||
(sm ? (chroff)(sm->smuse) : sd->sdlen))
|
||
sbx_split(sd, sb->sboff); /* Then split */
|
||
return(sd); /* And return ptr to 1st part */
|
||
break;
|
||
|
||
default:
|
||
return(0);
|
||
} /* End of switch */
|
||
return(1);
|
||
}
|
||
|
||
struct sdblk *
|
||
sbx_next (sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd, *sdf;
|
||
if((sdf = (sd = (sb=sbp)->sbcur)->slforw) == 0)
|
||
return((struct sdblk *)0);
|
||
sb->sbdot += (sd->sdmem ? (chroff)sd->sdmem->smuse : sd->sdlen);
|
||
sb->sboff = 0;
|
||
sd->sdflags &= ~SD_LOCK; /* Unlock current */
|
||
sdf->sdflags |= SD_LOCK; /* Lock next */
|
||
sb->sbcur = sdf;
|
||
return(sdf);
|
||
}
|
||
|
||
/* SBX_NORM(sb,mode) - Normalizes I/O position as desired.
|
||
* The SBBUF must have I/O disconnected (SBIOP==0).
|
||
* Adjusts SBCUR, SBDOT, and SBOFF so that SBOFF is guaranteed
|
||
* to point to a location in the current SD block.
|
||
* The mode flags determine action when there is more than
|
||
* one possible SD that could be pointed to, as is the case
|
||
* when the I/O pos falls on a block boundary (possibly with
|
||
* adjacent zero-length blocks as well).
|
||
* SKM_0BACK - Zero-skip direction.
|
||
* 0 = Skip forward over zero-length blocks.
|
||
* set = Skip backward over zero-length blocks.
|
||
* SKM_EOB - Block-end selection (applies after skipping done).
|
||
* 0 = Point to BOB (Beginning Of Block).
|
||
* set = Point to EOB (End Of Block).
|
||
* Returns the new current SD as a convenience.
|
||
* Notes:
|
||
* The SKM_0BACK flag value is a special hack to search in
|
||
* the right direction when SBOFF is initially 0.
|
||
* None of the mode flags have any effect if the I/O pos falls
|
||
* within a block.
|
||
* Perhaps this routine should flush the zero-length blks it
|
||
* finds, if they're not locked??
|
||
*/
|
||
struct sdblk *
|
||
sbx_norm(sbp,mode)
|
||
SBBUF *sbp;
|
||
int mode;
|
||
{ register struct sdblk *sd;
|
||
register struct smblk *sm;
|
||
register SBBUF *sb;
|
||
chroff len;
|
||
|
||
if((sd = (sb=sbp)->sbcur) == 0)
|
||
{ sb->sbdot = 0;
|
||
sb->sboff = 0;
|
||
return(sd);
|
||
}
|
||
sd->sdflags &= ~SD_LOCK; /* Unlock current blk */
|
||
|
||
if(sb->sboff >= (mode&01)) /* Hack hack to get right skip */
|
||
for(;;) /* Scan forwards */
|
||
{ if(sm = sd->sdmem) /* Get length of this blk */
|
||
len = sm->smuse;
|
||
else len = sd->sdlen;
|
||
if(sb->sboff <= len)
|
||
if(sb->sboff < len /* If == and fwd 0-skip, continue */
|
||
|| (mode&SKM_0BACK))
|
||
{ if((mode&SKM_EOB) /* Done, adjust to EOB? */
|
||
&& sb->sboff == 0 /* Yes, are we at BOB? */
|
||
&& sd->slback) /* and can do it? */
|
||
{ sd = sd->slback; /* Move to EOB */
|
||
sb->sboff = (sm = sd->sdmem)
|
||
? (chroff)(sm->smuse) : sd->sdlen;
|
||
sb->sbdot -= sb->sboff;
|
||
}
|
||
break;
|
||
}
|
||
if(sd->slforw == 0) /* At EOF? */
|
||
{ sb->sboff = len;
|
||
break;
|
||
}
|
||
sd = sd->slforw;
|
||
sb->sboff -= len;
|
||
sb->sbdot += len;
|
||
}
|
||
else /* Scan backwards */
|
||
for(;;)
|
||
{ if(sd->slback == 0) /* At BOF? */
|
||
{ sb->sboff = 0;
|
||
sb->sbdot = 0; /* Should already be 0, but... */
|
||
break;
|
||
}
|
||
sd = sd->slback;
|
||
if(sm = sd->sdmem) /* Get length of this blk */
|
||
len = sm->smuse;
|
||
else len = sd->sdlen;
|
||
sb->sbdot -= len;
|
||
if((sb->sboff += len) >= 0)
|
||
if(sb->sboff > 0 /* If == 0 and bkwd 0-skip, continue */
|
||
|| !(mode&SKM_0BACK))
|
||
{ if((mode&SKM_EOB) == 0 /* Done, adjust to BOB? */
|
||
&& sb->sboff == len /* Yes, are we at EOB? */
|
||
&& sd->slforw) /* and can do it? */
|
||
{ sd = sd->slforw; /* Move to BOB */
|
||
sb->sboff = 0;
|
||
sb->sbdot += len;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
sb->sbcur = sd;
|
||
sd->sdflags |= SD_LOCK;
|
||
return(sd);
|
||
}
|
||
|
||
|
||
struct sdblk *
|
||
sbx_beg(sdp)
|
||
struct sdblk *sdp;
|
||
{ register struct sdblk *sd, *sdx;
|
||
if(sd = sdp)
|
||
while(sdx = sd->slback)
|
||
sd = sdx;
|
||
return(sd);
|
||
}
|
||
|
||
|
||
sbx_smdisc(sbp)
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct smblk *sm;
|
||
register struct sdblk *sd;
|
||
|
||
sb = sbp;
|
||
if((sd = sb->sbcur) == 0
|
||
|| (sm = sd->sdmem) == 0)
|
||
return;
|
||
if(sb->sbflags&SB_WRIT)
|
||
{ sm->smuse = sb->sbiop - sm->smaddr;
|
||
sb->sbflags &= ~SB_WRIT;
|
||
}
|
||
sb->sboff = sb->sbiop - sm->smaddr;
|
||
sb->sbiop = 0;
|
||
sb->sbrleft = sb->sbwleft = 0;
|
||
}
|
||
|
||
sbx_sbrdy(sbp) /* Sets up SBIOP, SBRLEFT, SBWLEFT */
|
||
SBBUF *sbp;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd;
|
||
register struct smblk *sm;
|
||
|
||
if((sd = (sb=sbp)->sbcur) == 0
|
||
|| (sm = sd->sdmem) == 0)
|
||
return;
|
||
sd->sdflags |= SD_LOCK;
|
||
sb->sbiop = sm->smaddr + sb->sboff;
|
||
if(sb->sbrleft = sm->smuse - sb->sboff)
|
||
sb->sbwleft = 0;
|
||
else sb->sbwleft = sm->smlen - sm->smuse;
|
||
}
|
||
|
||
|
||
/* SBX_SCPY(sd,sdl) - Copies given sbstring, returns ptr to new sbstring.
|
||
* Only goes as far as sdl (last copied blk); 0 for entire sbstring.
|
||
*/
|
||
struct sdblk *
|
||
sbx_scpy(sdp,sdlast)
|
||
struct sdblk *sdp, *sdlast;
|
||
{ register struct sdblk *sd, *sd2, *sdn;
|
||
struct sdblk *sdr;
|
||
|
||
if((sd = sdp) == 0) return((struct sdblk *)0);
|
||
sdn = 0;
|
||
do {
|
||
sd->sdflags |= SD_LCK2;
|
||
sd2 = sbx_sdcpy(sd);
|
||
if(sd2->slback = sdn)
|
||
{ sdn->slforw = sd2;
|
||
sdn->sdflags &= ~SD_LOCKS;
|
||
}
|
||
else sdr = sd2; /* Save 1st */
|
||
sdn = sd2;
|
||
sd->sdflags &= ~SD_LCK2;
|
||
} while(sd != sdlast && (sd = sd->slforw));
|
||
sd2->slforw = 0;
|
||
sd2->sdflags &= ~SD_LOCKS;
|
||
return(sdr);
|
||
}
|
||
|
||
|
||
/* SBX_SDCPY(sd) - Copies given sdblk, returns ptr to new blk.
|
||
* Does not set locks, assumes caller does this (which it MUST,
|
||
* to avoid compaction lossage!)
|
||
*/
|
||
|
||
struct sdblk *
|
||
sbx_sdcpy(sdp)
|
||
struct sdblk *sdp;
|
||
{ register struct sdblk *sd, *sd2;
|
||
register struct smblk *sm, *sm2;
|
||
|
||
if((sd = sdp) == 0) return((struct sdblk *)0);
|
||
sd2 = sbx_ndget(); /* Get a free sdblk */
|
||
bcopy((SBMA)sd, (SBMA)sd2, sizeof(struct sdblk)); /* Copy sdblk data */
|
||
sd2->slforw = 0; /* Don't let it think it's on a list */
|
||
sd2->slback = 0;
|
||
if(sd2->sdfile) /* If has disk copy, */
|
||
{ sd->sdforw = sd2; /* Fix phys list ptrs */
|
||
sd2->sdback = sd;
|
||
if(sd2->sdforw)
|
||
sd2->sdforw->sdback = sd2;
|
||
}
|
||
if(sm = sd2->sdmem) /* If has in-core copy, try to */
|
||
{ if(sm2 = sbm_mget(sm->smuse,sm->smuse)) /* get mem for it */
|
||
{ bcopy(sm->smaddr,sm2->smaddr,sm->smuse);
|
||
sm2->smuse = sm->smuse;
|
||
sd2->sdmem = sm2; /* Point new sd to copy */
|
||
}
|
||
else /* Can't get mem... */
|
||
{ if(sd2->sdflags&SD_MOD)
|
||
sbx_aout(sd2,1); /* Swap out the blk */
|
||
sd2->sdmem = 0; /* Don't have incore copy */
|
||
}
|
||
}
|
||
return(sd2);
|
||
}
|
||
|
||
/* SBX_XCIS(sbp,coff,&sdp2,adot) - Internal routine to excise a sbstring,
|
||
* defined as everything between current location and given offset.
|
||
* SD to first sdblk is returned (0 if error)
|
||
* SD2 (address passed as 3rd arg) is set to last sdblk.
|
||
* Both are locked with LCK2 to ensure that pointers are valid.
|
||
* The current location at time of call is also returned via adot.
|
||
*/
|
||
struct sdblk *
|
||
sbx_xcis(sbp,num,asd2,adot)
|
||
SBBUF *sbp;
|
||
chroff num, *adot;
|
||
struct sdblk **asd2;
|
||
{ register SBBUF *sb;
|
||
register struct sdblk *sd, *sd2;
|
||
int dirb;
|
||
|
||
if((sb = sbp) == 0) return((struct sdblk *)0);
|
||
dirb = 0; /* Delete forward */
|
||
if(num == 0) return((struct sdblk *)0); /* Delete nothing */
|
||
if(num < 0) dirb++; /* Delete backward */
|
||
|
||
if((sd = (struct sdblk *)
|
||
sbx_ready(sb, (dirb ? SK_DELB : SK_DELF))) == 0)
|
||
return((struct sdblk *)0); /* Maybe nothing there */
|
||
sd->sdflags |= SD_LCK2; /* Lock up returned SD */
|
||
*adot = sb->sbdot; /* Save current location */
|
||
sb->sboff += num; /* Move to other end of range */
|
||
|
||
if((sd2 = (struct sdblk *)
|
||
sbx_ready(sb,(dirb ? SK_DELF : SK_DELB))) == 0)
|
||
{ sd->sdflags &= ~SD_LCK2; /* This shd never happen if */
|
||
return( /* we got this far, but... */
|
||
(struct sdblk *)sbx_err(0,"KILLN SD2 failed"));
|
||
}
|
||
sd2->sdflags |= SD_LCK2; /* Lock up other end of stuff */
|
||
|
||
/* SD and SD2 now delimit bounds of stuff to excise.
|
||
* Now do direction dependent fixups
|
||
*/
|
||
if(dirb)
|
||
{ /* Backward, current sbdot is ok but must get SD/SD2
|
||
* into first/last order. Also, due to nature of block
|
||
* splitups, a backward delete within single block will leave
|
||
* SD actually pointing at predecessor block.
|
||
*/
|
||
if(sd->slforw == sd2) /* If SD became pred, fix things. */
|
||
{ sd->sdflags &= ~SD_LCK2; /* Oops, unlock! */
|
||
sd = sd2;
|
||
}
|
||
else /* Just need to swap SD, SD2 ptrs. */
|
||
{ /* Goddamit why doesn't C have an */
|
||
/* exchange operator??? */
|
||
*asd2 = sd;
|
||
return(sd2);
|
||
}
|
||
}
|
||
*asd2 = sd2;
|
||
return(sd);
|
||
}
|
||
|
||
/* SBX_SPLIT(sd,chroff) - Splits block SD at point CHROFF (offset from
|
||
* start of block). SD remains valid; it is left locked.
|
||
* The smblk is split too, if one exists, and SMUSE adjusted.
|
||
* If offset 0, or equal to block length, the 1st or 2nd SD respectively
|
||
* will not have a smblk and its sdlen will be 0.
|
||
* (Note that if a smblk exists, a zero sdlen doesn't indicate much)
|
||
*/
|
||
struct sdblk *
|
||
sbx_split(sdp, coff)
|
||
struct sdblk *sdp;
|
||
chroff coff;
|
||
{ register struct sdblk *sd, *sdf, *sdx;
|
||
|
||
if((sd=sdp) == 0)
|
||
return((struct sdblk *)0);
|
||
sd->sdflags |= SD_LOCK;
|
||
if(sd->sdflags&SD_MOD) /* If block has been munged, */
|
||
sbx_npdel(sd); /* Flush from phys list now. */
|
||
sdf = sbx_ndget(); /* Get a sdblk node */
|
||
bcopy((SBMA)sd, (SBMA)sdf, (sizeof (struct sdblk))); /* Copy node */
|
||
/* Note that the flags are copied, so both sdblks are locked and
|
||
* safe from possible GC compaction during call to sbx_msplit...
|
||
*/
|
||
if(coff == 0) /* If offset was 0, */
|
||
{ /* then 1st SD becomes null */
|
||
if(sdf->sdfile) /* Fix up phys links here */
|
||
{ if(sdx = sdf->sdback)
|
||
sdx->sdforw = sdf;
|
||
else sdf->sdfile->sfptr1 = sdf;
|
||
if(sdx = sdf->sdforw)
|
||
sdx->sdback = sdf;
|
||
}
|
||
sdx = sd;
|
||
goto nulsdx;
|
||
}
|
||
else if(sd->sdmem)
|
||
if(coff >= sd->sdmem->smuse)
|
||
goto nulsdf;
|
||
else sdf->sdmem = sbx_msplit(sd->sdmem, (SBMO)coff);
|
||
else if(coff >= sd->sdlen)
|
||
nulsdf: { sdx = sdf;
|
||
nulsdx: sdx->sdforw = 0;
|
||
sdx->sdback = 0;
|
||
sdx->sdmem = 0;
|
||
sdx->sdfile = 0;
|
||
sdx->sdlen = 0;
|
||
sdx->sdaddr = 0;
|
||
goto nulskp;
|
||
}
|
||
if(sd->sdfile)
|
||
{ sdf->sdlen -= coff; /* Set size of remainder */
|
||
sdf->sdaddr += coff; /* and address */
|
||
sd->sdlen = coff; /* Set size of 1st part */
|
||
|
||
/* Link 2nd block into proper place in physical sequence.
|
||
* 1st block is already in right place. Search forward until
|
||
* find a block with same or higher disk address, and insert
|
||
* in front of it. If sdlen is zero, just flush the links,
|
||
* which is OK since the 1st block is what's pointed to anyway.
|
||
*/
|
||
if(sdf->sdlen > 0)
|
||
{ while((sdx = sd->sdforw) /* Find place to insert */
|
||
&& sdf->sdaddr > sdx->sdaddr)
|
||
sd = sdx;
|
||
sdf->sdback = sd; /* Link following sd. */
|
||
if(sdf->sdforw = sd->sdforw)
|
||
sdf->sdforw->sdback = sdf;
|
||
sd->sdforw = sdf;
|
||
sd = sdp; /* Restore pointer */
|
||
}
|
||
else
|
||
{ sdf->sdforw = 0;
|
||
sdf->sdback = 0;
|
||
sdf->sdfile = 0; /* Say no disk */
|
||
}
|
||
}
|
||
|
||
nulskp: sdf->slback = sd; /* Link in logical sequence */
|
||
if(sd->slforw)
|
||
sd->slforw->slback = sdf;
|
||
sd->slforw = sdf;
|
||
|
||
sdf->sdflags &= ~SD_LOCKS; /* Unlock 2nd but not 1st */
|
||
return(sd); /* Note sd, not sdf */
|
||
}
|
||
|
||
/* SBX_MSPLIT - Like sbm_split but never fails, and sets
|
||
* SMUSE values appropriately
|
||
*/
|
||
struct smblk *
|
||
sbx_msplit(smp, size)
|
||
struct smblk *smp;
|
||
SBMO size;
|
||
{ register struct smblk *sm, *smx;
|
||
register int lev;
|
||
|
||
lev = 0;
|
||
while((smx = sbm_split((sm = smp), size)) == 0)
|
||
sbx_comp(SB_BUFSIZ,lev++); /* Need to get some smblk nodes */
|
||
if(sm->smlen >= sm->smuse) /* Split across used portion? */
|
||
smx->smuse = 0; /* Nope, new blk is all free */
|
||
else
|
||
{ smx->smuse = sm->smuse - sm->smlen;
|
||
sm->smuse = sm->smlen;
|
||
}
|
||
return(smx);
|
||
}
|
||
|
||
/* SBX_NDEL - flush a SD and associated SM. Fixes up logical
|
||
* and physical links properly. Returns ptr to next logical SD.
|
||
* NOTE: if sd->slback does not exist, the returned SD is your
|
||
* only hold on the list, since the SD gets flushed anyway!
|
||
*/
|
||
struct sdblk *
|
||
sbx_ndel(sdp)
|
||
struct sdblk *sdp;
|
||
{ register struct sdblk *sd, *sdx;
|
||
register struct smblk *sm;
|
||
|
||
sd = sdp;
|
||
if(sm = sd->sdmem) /* If smblk exists, */
|
||
{ sbm_mfree(sm); /* flush it. */
|
||
sd->sdmem = 0;
|
||
}
|
||
if(sdx = sd->slback)
|
||
sdx->slforw = sd->slforw;
|
||
if(sd->slforw)
|
||
sd->slforw->slback = sdx; /* May be zero */
|
||
|
||
/* Logical links done, now hack phys links */
|
||
if(sd->sdfile) /* Have phys links? */
|
||
sbx_npdel(sd); /* Yes, flush from phys list */
|
||
|
||
sdx = sd->slforw;
|
||
sbx_ndfre(sd);
|
||
return(sdx);
|
||
}
|
||
|
||
sbx_npdel(sdp)
|
||
struct sdblk *sdp;
|
||
{ register struct sdblk *sd, *sdx;
|
||
register struct sbfile *sf;
|
||
|
||
if((sf = (sd=sdp)->sdfile) == 0)
|
||
return;
|
||
if(sdx = sd->sdback) /* Start of disk file? */
|
||
sdx->sdforw = sd->sdforw;
|
||
else
|
||
sf->sfptr1 = sd->sdforw;
|
||
if(sdx = sd->sdforw)
|
||
sdx->sdback = sd->sdback;
|
||
sd->sdfile = 0;
|
||
sd->sdlen = 0;
|
||
}
|
||
|
||
|
||
struct sdblk *sbx_nfl; /* Pointer to sdblk node freelist */
|
||
|
||
struct sdblk *
|
||
sbx_ndget() /* Like sbm_nget but never fails! */
|
||
{ register struct sdblk *sd;
|
||
register int lev;
|
||
|
||
lev = 0;
|
||
while((sd = sbx_nfl) == 0 /* Get a node */
|
||
/* If fail, make more */
|
||
&& (sd = sbm_nmak((sizeof (struct sdblk)),SM_DNODS)) == 0)
|
||
/* If still fail, try GC */
|
||
sbx_comp(sizeof(struct sdblk)*SM_DNODS,lev++);
|
||
|
||
sbx_nfl = sd->slforw; /* Take it off freelist */
|
||
sd->sdflags = SD_NID;
|
||
return(sd); /* Return ptr to it */
|
||
}
|
||
|
||
sbx_ndfre(sdp)
|
||
struct sdblk *sdp;
|
||
{ register struct sdblk *sd;
|
||
(sd = sdp)->sdflags = 0;
|
||
sd->slforw = sbx_nfl;
|
||
sbx_nfl = sd;
|
||
}
|
||
|
||
SBMA
|
||
sbx_malloc(size)
|
||
unsigned size;
|
||
{
|
||
register int lev;
|
||
register SBMA res;
|
||
|
||
lev = 0;
|
||
while((res = (SBMA)malloc(size)) == 0)
|
||
sbx_comp(size,lev++);
|
||
return(res);
|
||
}
|
||
|
||
struct smblk *
|
||
sbx_mget(cmin,cmax) /* like sbm_mget but never fails! */
|
||
SBMO cmin, cmax;
|
||
{ register struct smblk *sm;
|
||
register int lev, csiz;
|
||
|
||
lev = 0;
|
||
csiz = cmax;
|
||
for(;;)
|
||
{ if(sm = sbm_mget(csiz,cmax))
|
||
return(sm); /* Won right off... */
|
||
sbx_comp(csiz,lev++); /* Barf, invoke GC */
|
||
if(sm = sbm_mget(csiz,cmax)) /* Then try again */
|
||
return(sm);
|
||
if((csiz >>= 1) < cmin) /* If still short, reduce */
|
||
csiz = cmin; /* request down to min */
|
||
}
|
||
}
|
||
|
||
chroff sbv_taddr; /* Disk addr of place to write into (set by TSET) */
|
||
struct sdblk *sbv_tsd; /* SD that disk addr comes after (set by TSET) */
|
||
|
||
#define sbx_qlk(sd) (sd->sdflags&SD_LOCKS)
|
||
|
||
#if 0
|
||
This is the compaction routine, which is the key to the
|
||
entire scheme. Paging text to and from disk is trivial, but the
|
||
ability to merge blocks is the important thing since it allows
|
||
flushing the pointer information as well as the actual text! This
|
||
eliminates fragmentation as a fatal problem.
|
||
There are a variety of ways that storage can be reclaimed:
|
||
|
||
- "pure" in-core blocks can be flushed instantly.
|
||
- "impure" incore blocks can be written to tempfile storage and flushed.
|
||
- The SM node freelist can be compacted so as to flush memory which is
|
||
used for nothing but holding free nodes.
|
||
- The SD node freelist can be compacted, ditto.
|
||
- SBBUFs can be compacted, by:
|
||
- Merging logically & physically adjacent on-disk pieces
|
||
- merging logically & physically adjacent in-core pieces
|
||
- merging logically adjacent in-core pieces
|
||
- merging logically adjacent disk pieces, by reading in
|
||
and then writing out to tempfile storage.
|
||
Worst case would reduce whole sbstr to single tempfile block.
|
||
|
||
Problems:
|
||
What is "optimal" algorithm for typical usage?
|
||
Must go over all code to make sure right things get locked
|
||
and unlocked to avoid having rug pulled out from under.
|
||
Could have optional "registration table" for sbstruc; if exist
|
||
in table, can check during GC. If find one, can first
|
||
do sbx_smdisc and then repoint sbcur to 1st block,
|
||
with sbdot of 0 and sboff of sb_tell(). This allows
|
||
reducing whole thing to one block even tho "locked".
|
||
Never touch stuff locked with SD_LCK2, though.
|
||
Also may need way to protect the sbstr SD actually being
|
||
pointed to by current sbx routine processing.
|
||
Could have count of # nodes free for SM and SD; don''t GC
|
||
unless # is some number greater than size of a node block!
|
||
Have different levels of compaction; pass level # down thru calls
|
||
so as to invoke progressively sterner compaction measures.
|
||
Can invoke sbx_comp with any particular level!
|
||
Must have list somewhere of SBBUFs? or maybe OK to scan core
|
||
for SM_DNODS, then scan sdblks?
|
||
Screw: could happen that stuff gets flushed (cuz pure) or even
|
||
written out to tempfile, and then we have to read it back
|
||
in so as to compact more stuff into tempfile... how to avoid?
|
||
If pure stuff small and next to impure stuff, merge?
|
||
Some calls just want to get another free node and don''t need
|
||
new core. How to indicate this? How to decide between
|
||
freeing up used nodes, and creating new node freelist?
|
||
#endif /*COMMENT*/
|
||
/* Compact stuff.
|
||
* General algorithm for getting storage is:
|
||
* 1) allocate from freelist if enough there
|
||
* 2) find unlocked pure smblk to free up
|
||
* 3) find unlocked impure smblks, write out.
|
||
* 4) Compact stuff by reducing # of sdblks. This is key to scheme!
|
||
* Otherwise fragmentation will kill program.
|
||
* Maybe put age cnt in each sbstr? Bump global and set cntr each time
|
||
* sbstr gets major hacking (not just getc/putc).
|
||
*/
|
||
extern struct smblk *sbm_list;
|
||
sbx_comp(cmin,lev)
|
||
int cmin, lev;
|
||
{ int sbx_sdgc();
|
||
|
||
if(lev > 100) /* If program has no way to handle this, */
|
||
abort(); /* then simply blow up. */
|
||
if(lev > 10) /* Too many iterations? Try to warn. */
|
||
return(sbx_err(0,"GC loop, cannot free block of size %d",
|
||
cmin));
|
||
|
||
/* Step thru core hunting for SD node blocks */
|
||
sbm_nfor(SM_DNODS,sizeof(struct sdblk),sbx_sdgc,lev);
|
||
}
|
||
|
||
/* Do GC stuff on a sdblk. Guaranteed to exist, but may be locked */
|
||
sbx_sdgc(sdp,lev)
|
||
struct sdblk *sdp;
|
||
int lev;
|
||
{ register struct sdblk *sd, *sdf;
|
||
register struct smblk *sm;
|
||
struct smblk *smf, *sbm_exp ();
|
||
SBMO more;
|
||
|
||
sd = sdp;
|
||
if(sbx_qlk(sd)) return(0);
|
||
sm = sd->sdmem;
|
||
sdf = sd->slforw;
|
||
if (lev < 4) goto lev3;
|
||
|
||
/* Level 4 - write out everything possible */
|
||
/* Back up to start of sbstr */
|
||
while((sdf = sd->slback) && !sbx_qlk(sdf))
|
||
sd = sdf;
|
||
if((sdf = sd->slforw) == 0 /* If only 1 blk, ensure on disk */
|
||
|| sbx_qlk(sdf))
|
||
{ if(sm = sd->sdmem)
|
||
{ if(sd->sdflags&SD_MOD) /* If impure, */
|
||
sbx_aout(sd, 1); /* swap out the SD */
|
||
sbm_mfree(sm);
|
||
sd->sdmem = 0;
|
||
}
|
||
return(0);
|
||
}
|
||
/* At least two blocks in string. Set up for flushout. */
|
||
sbx_aout(sd, 0); /* Swapout as much of sbstring as possible */
|
||
return(0);
|
||
|
||
lev3: /* Level 3 - write out more */
|
||
lev2: /* Level 2 - write out all impure & small pure */
|
||
lev1: if(lev >= 1) /* Level 1 - merge small impure & small pure */
|
||
{ if(!sm || !sdf) return(0);
|
||
while(((smf = sdf->sdmem) && !(sdf->sdflags&SD_LOCKS)
|
||
&& (more = smf->smuse + sm->smuse) < SB_BUFSIZ) )
|
||
{ if(sm->smforw != smf
|
||
&& more > sm->smlen) /* If need more rm */
|
||
{ sm = sbm_exp(sm,more); /* Get it */
|
||
if(!sm) return(0); /* If none, stop */
|
||
sd->sdmem = sm;
|
||
}
|
||
bcopy(smf->smaddr,
|
||
sm->smaddr + sm->smuse, smf->smuse);
|
||
sm->smuse = more;
|
||
if(sm->smforw == smf)
|
||
{ sdf->sdmem = 0;
|
||
sbm_mmrg(sm); /* Merge */
|
||
if(sm->smlen > more+SB_SLOP)
|
||
sbm_mfree(sbm_split(sm, more));
|
||
/* Guaranteed to win since mmrg
|
||
* just freed a mem node */
|
||
}
|
||
sd->sdflags |= SD_MOD;
|
||
if(sdf = sbx_ndel(sdf))
|
||
continue;
|
||
return(0);
|
||
}
|
||
}
|
||
|
||
if(lev <= 0) /* Level 0 - free up large pure blocks */
|
||
/* Also merge blocks which are adjacent on disk */
|
||
{ if(sm)
|
||
{ if(sm->smuse == 0)
|
||
sd->sdlen = 0;
|
||
else if((sd->sdflags&SD_MOD) == 0
|
||
&& sm->smuse > 64)
|
||
{ sbm_mfree(sm);
|
||
sd->sdmem = 0;
|
||
goto lev0adj;
|
||
}
|
||
else goto lev0adj;
|
||
}
|
||
|
||
if(sd->sdlen == 0 /* Free zero blocks */
|
||
&& sd->slback) /* Make sure don't lose list */
|
||
{ sbx_ndel(sd);
|
||
if((sd = sdf) == 0)
|
||
return(0);
|
||
sdf = sd->slforw;
|
||
}
|
||
lev0adj: /* Merge blocks if adjacent on disk */
|
||
/* This is common after reading thru large chunks
|
||
* of a file but not modifying it much.
|
||
*/
|
||
if((sd->sdflags&SD_MOD) == 0 /* Pure */
|
||
&& sdf && (sdf->sdflags&(SD_LOCKS|SD_MOD)) == 0
|
||
&& sd->sdfile && (sd->sdfile == sdf->sdfile)
|
||
&& (sd->sdaddr + sd->sdlen) == sdf->sdaddr )
|
||
{ sd->sdlen += sdf->sdlen;
|
||
sbx_ndel(sdf); /* Flush 2nd */
|
||
if(sm = sd->sdmem)
|
||
{ sbm_mfree(sm);
|
||
sd->sdmem = 0;
|
||
}
|
||
}
|
||
return(0);
|
||
}
|
||
return(0);
|
||
}
|
||
|
||
/* SBX_AOUT - output ALL of a hackable sbstring starting at given sdblk.
|
||
* Note that code is careful to do things so that an abort at any
|
||
* time (e.g. write error) will still leave sbstring in valid state.
|
||
* Flag value:
|
||
* 0 - Writes out as many unlocked sdblks as possible, and merges
|
||
* so that resulting sdblk (same one pointed to by arg)
|
||
* incorporates all stuff written out.
|
||
* 1 - Writes out single sdblk indicated, whether unlocked or not.
|
||
* Doesn't free mem or merge anything; does update physlist
|
||
* and flags.
|
||
* 2 - Writes out all sdblks to specified FD/offset, no mods at all,
|
||
* not even to physlist or flags. Good for saving files
|
||
* when something seems wrong. (How to pass fd/off args?)
|
||
* (offset arg not implemented, no need yet; 0 assumed)
|
||
* Returns 0 if successful, else UNIX system call error number.
|
||
*/
|
||
|
||
sbx_aout(sdp,flag,fd)
|
||
struct sdblk *sdp;
|
||
int flag, fd;
|
||
{ register struct sdblk *sd;
|
||
register struct smblk *sm;
|
||
register int cnt;
|
||
int ifd, ofd, skflg, rem;
|
||
chroff inlen;
|
||
extern SBMA sbm_lowaddr; /* Need this from SBM for rndrem */
|
||
char buf[SB_BUFSIZ+16]; /* Get buffer space from stack! */
|
||
/* Allow extra for word-align reads. */
|
||
/* This should be +WDSIZE, but some */
|
||
/* C compilers (eg XENIX) can't handle */
|
||
/* "sizeof" arith in allocation stmts! */
|
||
|
||
/* This flag and the two ptrs below are needed because UNIX
|
||
* maintains only one I/O ptr per open file, and we can sometimes
|
||
* be reading from/writing to the swapout file at same time.
|
||
* Using DUP() to get a new FD (to avoid seeking back and forth)
|
||
* won't help since both FD's will use the same I/O ptr!!!
|
||
* Lastly, can't depend on returned value of LSEEK to push/pop
|
||
* ptr, since V6 systems don't implement tell() or lseek() directly.
|
||
* So we have to do it by hand...
|
||
*/
|
||
int botchflg;
|
||
chroff outptr, inptr;
|
||
|
||
if((sd = sdp)==0) return;
|
||
ofd = sbv_tf.sffd; /* Default output FD */
|
||
if(flag==0)
|
||
{ sbx_tset(sbx_qlen(sd),0);/* Find place for whole string */
|
||
outptr = sbv_taddr; /* We'll have to update wrt ptr */
|
||
}
|
||
else if (flag==1) /* Single SD block, so it's reasonable to
|
||
* try aligning the output with the input. */
|
||
{ if(sm = sd->sdmem)
|
||
{ cnt = rndrem(sm->smaddr - sbm_lowaddr);
|
||
sbx_tset((chroff)(sm->smuse),cnt);
|
||
}
|
||
else
|
||
{ cnt = rndrem(sd->sdaddr);
|
||
sbx_tset(sd->sdlen, cnt);
|
||
}
|
||
outptr = sbv_taddr; /* We'll have to update wrt ptr */
|
||
}
|
||
else /* Outputting a whole sbstring to a file */
|
||
{ ofd = fd;
|
||
outptr = 0;
|
||
}
|
||
|
||
for(; sd;)
|
||
{ if(flag==0 && sbx_qlk(sd))
|
||
break; /* Stop when hit locked sdblk */
|
||
if(sm = sd->sdmem)
|
||
{ if(cnt = sm->smuse)
|
||
if(write(ofd, sm->smaddr, cnt) != cnt)
|
||
return(sbx_err(errno,"Swapout wrt err"));
|
||
outptr += cnt;
|
||
if(flag==0)
|
||
{ sd->sdmem = 0; /* Flush the mem used */
|
||
sbm_mfree(sm);
|
||
}
|
||
inlen = cnt;
|
||
}
|
||
else if(inlen = sd->sdlen)
|
||
{ if(sd->sdfile == 0)
|
||
return(sbx_err(errno,"Sdfile 0, SD %o",sd));
|
||
/* Foo on UNIX */
|
||
botchflg = ((ifd = sd->sdfile->sffd) == ofd) ? 1 : 0;
|
||
skflg = 1; /* Always seek first time */
|
||
inptr = sd->sdaddr;
|
||
/* Efficiency hack - set up for first read so that
|
||
* transfer is word-aligned and terminates at end
|
||
* of a disk block.
|
||
*/
|
||
rem = rndrem(inptr); /* Get alignment */
|
||
cnt = SB_BUFSIZ - (int)(inptr%SB_BUFSIZ);
|
||
while(inlen > 0)
|
||
{
|
||
if(inlen < cnt) cnt = inlen;
|
||
if(!sbx_rdf(ifd, buf+rem, cnt, skflg, inptr))
|
||
return(sbx_err(errno,"Swapout err, SD %o",sd));
|
||
/* Further seeks depend on botch setting */
|
||
if(skflg = botchflg)
|
||
{ if(lseek(ofd,outptr,0) < 0)
|
||
return(sbx_err(errno,
|
||
"Swapout sk err"));
|
||
inptr += cnt;
|
||
}
|
||
if(write(ofd, buf+rem, cnt) != cnt)
|
||
return(sbx_err(errno,
|
||
"Swapout wrt err"));
|
||
outptr += cnt;
|
||
inlen -= cnt;
|
||
cnt = SB_BUFSIZ; /* Now can use full blocks */
|
||
rem = 0; /* Aligned nicely, too! */
|
||
}
|
||
inlen = sd->sdlen;
|
||
}
|
||
|
||
/* Text written out, now merge block in */
|
||
if(flag == 2) /* No merge if saving file */
|
||
goto donxt;
|
||
if(sd != sdp) /* First block? */
|
||
{ sdp->sdlen += inlen; /* No, simple merge */
|
||
sd = sbx_ndel(sd); /* Flush, get next */
|
||
continue;
|
||
}
|
||
|
||
/* Handle 1st block specially */
|
||
if(sd->sdfile /* Unlink from phys list */
|
||
&& sd != sbv_tsd) /* Don't unlink if self */
|
||
sbx_npdel(sd);
|
||
sd->sdlen = inlen;
|
||
sd->sdfile = &sbv_tf;
|
||
sd->sdaddr = sbv_taddr; /* Set from sbx_tset val */
|
||
sd->sdflags &= ~SD_MOD; /* On disk, no longer modified */
|
||
|
||
/* Now insert into phys list at specified place */
|
||
if(sd == sbv_tsd) /* If already same place */
|
||
goto next; /* Skip linkin. */
|
||
if(sd->sdback = sbv_tsd)
|
||
{ sd->sdforw = sbv_tsd->sdforw;
|
||
sd->sdback->sdforw = sd;
|
||
}
|
||
else
|
||
{ sd->sdforw = sbv_tf.sfptr1;
|
||
sbv_tf.sfptr1 = sd;
|
||
}
|
||
if(sd->sdforw)
|
||
sd->sdforw->sdback = sd;
|
||
|
||
next: if(flag==1) /* If only doing 1 sdblk, */
|
||
break; /* stop here. */
|
||
donxt: sd = sd->slforw; /* Done with 1st, get next */
|
||
}
|
||
return(0); /* Win return, no errors */
|
||
}
|
||
|
||
/* Returns hackable length of a sbstring (ends at EOF or locked block) */
|
||
chroff
|
||
sbx_qlen(sdp)
|
||
struct sdblk *sdp;
|
||
{ register struct sdblk *sd;
|
||
register struct smblk *sm;
|
||
chroff len;
|
||
|
||
len = 0;
|
||
for(sd = sdp; sd && !sbx_qlk(sd); sd = sd->slforw)
|
||
if(sm = sd->sdmem)
|
||
len += (chroff)sm->smuse;
|
||
else len += sd->sdlen;
|
||
return(len);
|
||
}
|
||
|
||
|
||
/* SBX_TSET - finds a place on temp swapout file big enough to hold
|
||
* given # of chars. Sets SBV_TADDR to that location, as well
|
||
* as seeking to it so the next write call will output there.
|
||
* This location is guaranteed to have the requested
|
||
* byte alignment (0 = word-aligned).
|
||
*/
|
||
sbx_tset(loff, align)
|
||
chroff loff;
|
||
int align;
|
||
{ register int fd;
|
||
|
||
if(sbv_tf.sffd <= 0)
|
||
{ /* Must open the temp file! */
|
||
/* Temporary file mechanism is system-dependent. Eventually this
|
||
** will probably require inclusion of a true c-env header file; for the
|
||
** time being, we can cheat a little by checking O_T20_WILD, which will
|
||
** be defined by <sys/file.h> on TOPS-20. Otherwise, we assume we are
|
||
** on a real Unix.
|
||
*/
|
||
#ifdef O_T20_WILD
|
||
extern char *tmpnam(); /* Use ANSI function */
|
||
fd = open(tmpnam((char *)NULL),
|
||
O_RDWR | O_CREAT | O_TRUNC | O_BINARY);
|
||
if(fd < 0)
|
||
return(sbx_err(0,"Swapout creat err"));
|
||
#else /* Real Unix */
|
||
static char fcp[] = "/tmp/sbd.XXXXXX";
|
||
if((fd = creat(mktemp(fcp),0600)) < 0)
|
||
return(sbx_err(0,"Swapout creat err"));
|
||
/* Must re-open so that we can both read and write to it */
|
||
close(fd);
|
||
if((fd = open(fcp,2)) < 0)
|
||
return(sbx_err(0,"Swapout open err"));
|
||
unlink(fcp); /* Set so it vanishes when we do */
|
||
#endif
|
||
|
||
sbv_tf.sffd = fd; /* Initialize the sbfile struct */
|
||
sbv_tf.sfptr1 = 0;
|
||
sbv_ftab[fd] = &sbv_tf; /* Record in table of all sbfiles */
|
||
sbv_taddr = 0; /* "Return" this value */
|
||
return; /* Ignore alignment for now */
|
||
}
|
||
sbv_tsd = sbx_ffnd(&sbv_tf, loff+align, &sbv_taddr);
|
||
sbv_taddr += align;
|
||
if(lseek(sbv_tf.sffd, sbv_taddr, 0) < 0)
|
||
return(sbx_err(0,"Swapout seek err: (%d,%ld,0) %d %s",
|
||
sbv_tf.sffd, sbv_taddr, errno, strerror(errno)));
|
||
|
||
}
|
||
|
||
/* SBX_FFND - searches disk list of given file for free space of
|
||
* at least size chars. Note that list must be sorted by ascending
|
||
* disk addrs in order for this to work! If sdaddrs are only
|
||
* changed in SBX_SPLIT this will be true.
|
||
* Sets "aloc" to disk address for writing (this is guaranteed to
|
||
* be word-aligned, for efficiency), and returns SD ptr to
|
||
* block which this addr should follow in the physical list. If ptr
|
||
* is 0, it means addr should be 1st thing in list.
|
||
*/
|
||
struct sdblk *
|
||
sbx_ffnd(sfp, size, aloc)
|
||
SBFILE *sfp;
|
||
chroff size, *aloc;
|
||
{ register struct sdblk *sd, *sds, *sdl;
|
||
chroff cur;
|
||
|
||
cur = 0;
|
||
sds = 0;
|
||
sd = sfp->sfptr1;
|
||
redo: for(; sd ; sd = (sds=sd)->sdforw)
|
||
{ if(cur < sd->sdaddr) /* Gap seen? */
|
||
{ if(size <= (sd->sdaddr - cur)) /* Yes, big enuf? */
|
||
break; /* Yup! */
|
||
} /* No, bump. */
|
||
else if(cur >=(sd->sdaddr + sd->sdlen)) /* No gap but chk */
|
||
continue; /* No overlap, ok */
|
||
/* Bump to next possible gap. */
|
||
cur = sd->sdaddr + sd->sdlen;
|
||
cur = (long)rndup(cur); /* Round up to word boundary! */
|
||
}
|
||
*aloc = cur; /* Return winning addr */
|
||
|
||
/* Perform verification check -- make sure this really is OK
|
||
* and complain if not. If this never blows up, eventually can
|
||
* take the check out.
|
||
*/
|
||
sdl = sd;
|
||
for(sd = sfp->sfptr1; sd; sd = sd->sdforw)
|
||
{ if(cur < sd->sdaddr)
|
||
{ if(size <= (sd->sdaddr - cur))
|
||
continue;
|
||
}
|
||
else if(cur >= (sd->sdaddr + sd->sdlen))
|
||
continue;
|
||
|
||
sbx_err(0,"FFND blew it, but recovered. SD %o siz %ld",
|
||
sd, size);
|
||
sd = (sds = sdl)->sdforw;
|
||
goto redo;
|
||
}
|
||
|
||
|
||
return(sds); /* Return ptr to block this addr follows */
|
||
}
|
||
|
||
sbx_rdf(fd,addr,cnt,skflg,loc)
|
||
register int fd;
|
||
char *addr;
|
||
int skflg;
|
||
chroff loc;
|
||
{ register int rres, eres;
|
||
long lres;
|
||
char *errtyp, *ftyp;
|
||
chroff curlen;
|
||
|
||
errno = 0;
|
||
if(skflg && (lres = lseek(fd, (long)loc, 0)) == -1)
|
||
{ errtyp = "Sk err";
|
||
goto errhan;
|
||
}
|
||
if((rres = read(fd, addr, cnt)) != cnt)
|
||
{ lres = rres;
|
||
errtyp = "Rd err";
|
||
goto errhan;
|
||
}
|
||
return(rres);
|
||
errhan: /* Handle read or seek error */
|
||
eres = errno;
|
||
if(fd == sbv_tf.sffd) /* See if dealing with swapout file */
|
||
{ ftyp = "(swap)";
|
||
curlen = 0;
|
||
}
|
||
else { /* No, normal buffer file. */
|
||
ftyp = "";
|
||
curlen = sbx_fdlen(fd);
|
||
if(sbv_ftab[fd] &&
|
||
(curlen != sbv_ftab[fd]->sflen)) /* File changed? */
|
||
if(sbx_rugpull(fd)) /* Yes, handle special case */
|
||
return(cnt); /* Allow "win" return */
|
||
}
|
||
sbx_err(0,"%s %d:%s, %ld:(%d%s,%o,%d)=%ld (fl %ld)",
|
||
errtyp, eres, strerror(eres),
|
||
loc, fd, ftyp, addr, cnt, lres,
|
||
curlen);
|
||
return(0);
|
||
}
|
||
|
||
/* SBX_RUGPULL(fd) - Special routine called when package detects that
|
||
* the file indicated by the fd has changed since its original
|
||
* opening. This can happen when a file is over-written by some
|
||
* other program (ED, for example).
|
||
* This means that all sdblks which reference this fd
|
||
* are probably bad. Pass special error back up to the calling
|
||
* program to give it a chance at doing something.
|
||
* Extra credit: scan all sdblks and unpurify all which point to this
|
||
* file, so as to protect everything we still remember about it.
|
||
* Otherwise a GC could flush pure in-core portions.
|
||
*/
|
||
sbx_rugpull(fd) /* FD already known to have entry in sbv_ftab */
|
||
register int fd;
|
||
{ int sbx_unpur();
|
||
|
||
/* First scan all sdblks to save what we still have. */
|
||
/* This does NOT remove the sdfile pointer, so we can still
|
||
* find blocks that are affected. */
|
||
sbm_nfor(SM_DNODS, sizeof(struct sdblk), sbx_unpur, sbv_ftab[fd]);
|
||
|
||
if((int)sbv_debug == 1 || !sbv_debug)
|
||
return(0); /* Nothing else we can do */
|
||
return((*sbv_debug)(2,(int *)0,"",fd)); /* Let caller handle it */
|
||
}
|
||
sbx_unpur(sd, sf) /* Auxiliary routine for SBX_RUGPULL */
|
||
register struct sdblk *sd;
|
||
register struct sbfile *sf;
|
||
{ if(sd->sdfile == sf /* If sdblk belongs to affected file */
|
||
&& sd->sdmem) /* and has in-core version of text, */
|
||
sd->sdflags |= SD_MOD; /* then ensure core version is used */
|
||
}
|
||
|
||
sbx_err(val,str,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)
|
||
char *str;
|
||
{ int *sptr;
|
||
|
||
sptr = (int *) &sptr; /* Point to self on stack */
|
||
sptr += 5; /* Point to return addr */
|
||
if((int)sbv_debug == 1)
|
||
{ abort();
|
||
}
|
||
if(sbv_debug)
|
||
(*sbv_debug)(1,*sptr,str,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12);
|
||
return(val);
|
||
}
|