2196 lines
		
	
	
		
			65 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2196 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);
 | ||
| }
 | 
