mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1583 lines
49 KiB
1583 lines
49 KiB
/*** doclib.c - standard DH library routines
|
|
*
|
|
* This file contains the functions that implement folders
|
|
* and documents as abstract data types. While this implementation
|
|
* is intended to be portable, it is not definitive.
|
|
*
|
|
* This implementation supports a canonical folder format;
|
|
* the canonical folder format specifies the ordering of bytes
|
|
* within a short and long; shorts are assumed to be two bytes
|
|
* and longs are assumed to be four bytes. This canonical format
|
|
* makes it possible to read folders created on one architecture
|
|
* on another architecture; this is particularly important in
|
|
* a file-sharing network where the host cpus have different
|
|
* natural byte order.
|
|
*
|
|
* Functions exported from this file:
|
|
* getfolder - prepare a DH folder for manipulation
|
|
* putfolder - close a folder
|
|
* getname - get a DH name for a folder
|
|
* getfldlen - get an upper bound on highest docid in folder
|
|
* getdoc - get a document
|
|
* scanfolder - scan the existing documents
|
|
* putdoc - close a document
|
|
* deldoc - make a document go away
|
|
* getflags - return the attribute flags for a document
|
|
* putflags - set the attribute flags for a document
|
|
* gettext - write document to stream
|
|
* gethdr - get header block
|
|
* getbdy - write the body of the document to the passed file handle
|
|
* puttext - replace contents of a document
|
|
* putbdy - replace the body of a document
|
|
* puthdr - set new header block
|
|
* getid - get the name of a document
|
|
* gethdrlen - get length of a document header
|
|
* getbdylen - get length of a document body
|
|
* readbdy - read data from body into user buffer
|
|
* seekbdy - set address readbdy will read from
|
|
*/
|
|
|
|
// Convention Note: /*** means public, /** means private, there */
|
|
// is an awk script that makes a doc file from
|
|
// the source based on this.
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <io.h>
|
|
#include <share.h>
|
|
#include <sys\types.h>
|
|
#include <sys\stat.h>
|
|
#include "dh.h"
|
|
#include "dhint.h"
|
|
#include "dherror.h"
|
|
|
|
#ifdef XENIX
|
|
#define O_BINARY 0 /* null or in constant */
|
|
#endif
|
|
|
|
/*** Memory Allocation Hooks */
|
|
VOID * (_CRTAPI1 *dh_alloc)(size_t) = malloc;
|
|
VOID (_CRTAPI1 *dh_free)(PVOID) = free;
|
|
#define memalloc(size) (*dh_alloc)(size)
|
|
#define memfree(object) (*dh_free)(object)
|
|
|
|
|
|
#define SucRet(a) { dherrno = DHERR_NOERR; return a; }
|
|
|
|
//
|
|
// BUGBUG - dbghook for catching file op failures
|
|
//
|
|
#define dbgHook if (inWatchArea) bpHook ( dherrno );
|
|
|
|
#define ErrRet(a) { dherrno = a; dbgHook; return ERROR; }
|
|
#define ErrRetCast(a,b) { dherrno = a; dbgHook; return b ERROR; }
|
|
#define ErrRetCu(a,b) { dherrno = a; b; dbgHook; return ERROR; }
|
|
#define ErrRetCuCast(a,b,c) { dherrno = a; b; dbgHook; return c ERROR; }
|
|
|
|
//
|
|
// BUGBUG - flag for message add failure
|
|
//
|
|
|
|
extern BOOL inWatchArea;
|
|
|
|
/*
|
|
** Publics
|
|
*/
|
|
|
|
INT dherrno;
|
|
|
|
/*
|
|
** Globals
|
|
*/
|
|
|
|
static Folder foldertab[NFOLDERS];
|
|
static Document doctab[NDOCS];
|
|
static PSTR dhpath = NULL;
|
|
|
|
/*
|
|
** Forward functions
|
|
*/
|
|
|
|
static PSTR pathcat(PSTR *pathpp, PSTR name);
|
|
static Folder *fhtofp(Fhandle fh);
|
|
static INT mkfold(PSTR name, Folder *fp, INT oper, INT share);
|
|
static INT pathsrch(PSTR name, Folder *fp, INT oper, INT share);
|
|
static USHORT setnipe(VOID);
|
|
static UINT setsize(USHORT nipe);
|
|
static Document *dhtodp(Dhandle dh);
|
|
static PSTR blankline(PSTR cp);
|
|
static PSTR endbline(PSTR cp);
|
|
static INT addextent(Folder *fp, INT number);
|
|
static VOID clearextent(Folder *fp);
|
|
static INT flushextent(Folder *fp);
|
|
static INT getextent(Folder *fp, INT number, INT flag);
|
|
static INT readindex(Folder *fp, Docid docid, Index *ip);
|
|
static INT writeindex(Folder *fp, Docid docid, Index *ip);
|
|
|
|
//
|
|
// BUGBUG - hook for catching file op failures
|
|
//
|
|
void bpHook ( /* common error breakpoint hook */
|
|
INT errorNum
|
|
) {
|
|
|
|
#if DBG
|
|
DbgPrint ("BpHook in DocLib - dherrno = %d, GetLastError = %d \n",
|
|
errorNum, GetLastError() );
|
|
#endif
|
|
}
|
|
|
|
|
|
/*** getfolder - prepare a DH folder for manipulation
|
|
*
|
|
* folderhand = getfolder(foldername, function, oper)
|
|
*
|
|
* get a folder handle for the folder foldername
|
|
* function can take one of the following values:
|
|
* FLD_CREATE create a new folder
|
|
* FLD_SPEC open existing folder
|
|
* oper can be either
|
|
* FLD_READONLY open for read, deny-write
|
|
* FLD_READWRITE open for read-write, deny_all
|
|
*/
|
|
Fhandle getfolder(PSTR name, SHORT func, INT oper)
|
|
{
|
|
Folder *fp;
|
|
INT share;
|
|
|
|
switch (oper) {
|
|
|
|
case FLD_READONLY:
|
|
share = SH_DENYWR;
|
|
oper = O_RDONLY;
|
|
break;
|
|
|
|
case FLD_READWRITE:
|
|
share = SH_DENYRW;
|
|
oper = O_RDWR;
|
|
break;
|
|
|
|
default:
|
|
ErrRet(DHERR_BADFUNC);
|
|
}
|
|
|
|
|
|
|
|
|
|
/* find first free slot */
|
|
for (fp = foldertab; fp < &foldertab[NFOLDERS]; fp += 1) {
|
|
if (fp->f_flags == 0)
|
|
break;
|
|
}
|
|
|
|
switch(func) {
|
|
|
|
case FLD_SPEC: /* open a specific folder */
|
|
if ( pathsrch(name, fp, oper, share) == ERROR )
|
|
return ERROR;
|
|
break;
|
|
|
|
case FLD_CREATE: /* create a new subfolder */
|
|
if ( mkfold(name, fp, oper, share) == ERROR )
|
|
return ERROR;
|
|
break;
|
|
|
|
default: /* bad action */
|
|
ErrRet(DHERR_BADFUNC);
|
|
}
|
|
|
|
|
|
/* ok, set it up */
|
|
fp->f_flags |= F_BUSY;
|
|
SucRet((Fhandle)(fp - foldertab));
|
|
}
|
|
|
|
|
|
/** setnipe - compute how many index entries to use per extent
|
|
*
|
|
* setnipe decides how many index entries per extent should be used
|
|
* when we create a new folder. Currently, it always simply returns
|
|
* a fixed, per-system value.
|
|
*
|
|
*/
|
|
static USHORT setnipe (VOID)
|
|
{
|
|
return DEFAULT_NIPE;
|
|
}
|
|
|
|
/** mkfold - create a new folder
|
|
*
|
|
* mkfold creates a new, empty folder. The difficult part
|
|
* is determining the path name of the new folder. The path
|
|
* name is determined by the following rules:
|
|
* 1. If the name passed to mkfold contains a PATHSEP then
|
|
* the name passed is the path name used.
|
|
* 2. Otherwise, the name is generated by concatenating the
|
|
* first element of DHPATH with the passed name.
|
|
*
|
|
* Folders are created with mode 666 (modified by the creator's
|
|
* umask).
|
|
*
|
|
* Entry: name = name of new folder (see above)
|
|
* fp = pointer to folder slot to fill
|
|
*
|
|
* Return: OK or ERROR
|
|
*/
|
|
static INT mkfold(PSTR name, Folder *fp, INT oper, INT share)
|
|
{
|
|
PSTR work;
|
|
INT len;
|
|
|
|
if ( strchr(name, PATHSEP) != NULL ) {
|
|
fp->f_name = _strdup(name);
|
|
} else {
|
|
// if ( dhpath == NULL && (dhpath = getenv("DHPATH")) == NULL )
|
|
if ( dhpath == NULL && (dhpath = getenvOem("DHPATH")) == NULL )
|
|
dhpath = ".";
|
|
|
|
if ( (work = strchr(dhpath, PATHBRK)) == NULL )
|
|
len = strlen(dhpath);
|
|
else
|
|
len = (INT)(work - dhpath)/sizeof(CHAR);
|
|
|
|
/* allocate space; one CHAR for null terminator, one for PATHSEP */
|
|
if ((fp->f_name = memalloc((len + strlen(name) + 2) * sizeof(CHAR))) == NULL)
|
|
ErrRet(DHERR_NOMEM);
|
|
|
|
/* build the canonical name */
|
|
work = &(fp->f_name[len]);
|
|
if ( len ) {
|
|
strncpy(fp->f_name, dhpath, len );
|
|
*work++ = PATHSEP;
|
|
}
|
|
strcpy(work, name);
|
|
}
|
|
|
|
oper |= O_CREAT | O_EXCL | O_BINARY;
|
|
if ((fp->f_fd = _sopen(fp->f_name,oper,share,S_IREAD|S_IWRITE)) < 0){
|
|
/* fprintf(stderr, "Can't create folder '%s'.\n", fp->f_name); */
|
|
memfree(fp->f_name);
|
|
ErrRet(DHERR_BADOPEN);
|
|
}
|
|
fp->f_control.c_magic = MAGIC;
|
|
fp->f_control.c_numdoc = 0;
|
|
fp->f_control.c_nipe = setnipe ();
|
|
fp->f_control.c_version = VERSION;
|
|
fp->f_extsize = setsize(fp->f_control.c_nipe);
|
|
if (write(fp->f_fd,(PBYTE)&fp->f_control,
|
|
sizeof(Control))!=sizeof(Control)) {
|
|
memfree(fp->f_name);
|
|
ErrRet(DHERR_BADWRITE);
|
|
}
|
|
if ((fp->f_extent = (Extent *)memalloc(fp->f_extsize)) == NULL) {
|
|
memfree(fp->f_name);
|
|
ErrRet(DHERR_NOMEM);
|
|
}
|
|
clearextent(fp);
|
|
fp->f_extpos = sizeof(Control);
|
|
fp->f_extnum = 0;
|
|
if (write(fp->f_fd, (PBYTE)fp->f_extent,
|
|
fp->f_extsize) != (INT)fp->f_extsize) {
|
|
memfree((char *)fp->f_extent);
|
|
memfree(fp->f_name);
|
|
ErrRet(DHERR_BADWRITE);
|
|
}
|
|
fp->f_flags = 0;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
|
|
/** pathsrch - search along DHPATH for a folder
|
|
*
|
|
* pathsrch tries to locate a folder, using DHPATH if necessary.
|
|
* If the name that is passed contains a PATHSEP, that is the name
|
|
* used. Otherwise, the folder is searched for in each directory
|
|
* specified in DHPATH.
|
|
*
|
|
* Entry: name = name of folder (see above)
|
|
* fp = pointer to folder slot to fill
|
|
*
|
|
* Return: ERROR or OK
|
|
*/
|
|
static INT pathsrch(PSTR name, Folder *fp, INT oper, INT share)
|
|
{
|
|
PSTR path;
|
|
|
|
oper |= O_BINARY;
|
|
|
|
if ( strchr(name, PATHSEP) != NULL ) {
|
|
fp->f_name = _strdup(name);
|
|
if ((fp->f_fd = _sopen(fp->f_name, oper, share, 0)) < 0 ) {
|
|
memfree(fp->f_name);
|
|
ErrRet(DHERR_BADOPEN);
|
|
}
|
|
} else {
|
|
fp->f_fd = -1;
|
|
// if ( dhpath == NULL && (dhpath = getenv("DHPATH")) == NULL )
|
|
if ( dhpath == NULL && (dhpath = getenvOem("DHPATH")) == NULL )
|
|
dhpath = ".";
|
|
path = dhpath;
|
|
|
|
while ( (fp->f_name = pathcat(&path, name)) != NULL ) {
|
|
if ((fp->f_fd = _sopen(fp->f_name,oper,share,0)) >= 0 )
|
|
break;
|
|
memfree(fp->f_name);
|
|
}
|
|
if ( fp->f_fd < 0 )
|
|
ErrRet(DHERR_BADOPEN);
|
|
}
|
|
|
|
|
|
if (read(fp->f_fd, (PBYTE)&fp->f_control,
|
|
sizeof(Control)) != sizeof(Control))
|
|
ErrRet(DHERR_FLDCORRUPT);
|
|
|
|
if ( (fp->f_control.c_magic != MAGIC) ||
|
|
(fp->f_control.c_version != VERSION) )
|
|
{
|
|
close(fp->f_fd);
|
|
memfree(fp->f_name);
|
|
ErrRet(DHERR_FLDCORRUPT);
|
|
}
|
|
fp->f_extnum = -1;
|
|
fp->f_extent = NULL;
|
|
fp->f_extsize = setsize(fp->f_control.c_nipe);
|
|
fp->f_extpos = 0;
|
|
fp->f_flags = 0;
|
|
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/** pathcat - help contruct paths for pathsrc
|
|
*
|
|
* This function is used to construct the sequence of pathnames
|
|
* that represent the potential names of a file. The first argument
|
|
* is a pointer to a pointer to a string that specifies the search
|
|
* path. The second argument is the file name of the file.
|
|
* Each call returns a pointer to a memalloc'ed string that is the
|
|
* the concatenation of the first part of the search path and the
|
|
* file name. The search path pointer is is adjusted to point
|
|
* to the next element in the search path.
|
|
*
|
|
* Entry: pathpp = ptr to ptr to search path
|
|
* name = ptr to file name
|
|
*
|
|
* Return: pointer to memalloc'ed string containing pathname, or NULL
|
|
*
|
|
* Side effects: *pathpp is adjusted to point to next element in
|
|
* search path, if any
|
|
*
|
|
*/
|
|
static PSTR pathcat(PSTR *pathpp, PSTR name)
|
|
{
|
|
INT len;
|
|
|
|
PSTR end, work, rv;
|
|
|
|
if ( **pathpp == '\0' )
|
|
return NULL;
|
|
if ( (end = strchr(*pathpp, PATHBRK)) != NULL )
|
|
len = (INT)(end - *pathpp)/sizeof(CHAR);
|
|
else
|
|
len = strlen(*pathpp);
|
|
|
|
/* allocate space; one CHAR for null terminator, one for PATHSEP */
|
|
rv = (PSTR)memalloc((len + strlen(name) + 2) * sizeof(CHAR));
|
|
|
|
if ( rv != NULL) {
|
|
/* build the canonical name */
|
|
work = &rv[len];
|
|
if ( len ) {
|
|
strncpy(rv, *pathpp, len);
|
|
*work++ = PATHSEP;
|
|
}
|
|
strcpy(work, name);
|
|
|
|
/* adjust the path pointer for next time */
|
|
*pathpp += len;
|
|
if ( **pathpp == PATHBRK )
|
|
*pathpp += 1;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
|
|
/** setsize - return size of extent, based on nipe, static sizes.
|
|
*
|
|
* setsize subtracts the size of one index entry from the size of Extent,
|
|
* which yields the TRUE size of the static part of an extent. It then
|
|
* adds the number of index entries per extent (nipe) for this folder
|
|
* times the size of and index entry. This yields the true size of
|
|
* an extent for a particular folder.
|
|
*
|
|
*/
|
|
static UINT setsize(USHORT nipe)
|
|
{
|
|
UINT size;
|
|
|
|
size = sizeof(Extent) - sizeof(Index);
|
|
size += nipe * sizeof(Index);
|
|
return size;
|
|
}
|
|
|
|
|
|
/*** putfolder - close a folder
|
|
*
|
|
* putfolder indicates that this folder is no longer going
|
|
* to be manipulated. Thus, we can now deallocate any resources
|
|
* associated with that folder, after commiting all buffered data
|
|
* to the disk file.
|
|
*
|
|
* NOTE: This version always blows away handles and the like,
|
|
* but it will at least REPORT errors. Need more work
|
|
* here to allow recovery from them.
|
|
*
|
|
* Entry: fh = Fhandle of folder to close
|
|
* Return: ERROR or OK
|
|
*
|
|
*/
|
|
INT putfolder(Fhandle fh)
|
|
{
|
|
Folder *fp;
|
|
INT ErrCount = 0;
|
|
|
|
if ((fp = fhtofp(fh)) == NULL)
|
|
ErrRet(DHERR_BADFLDHAND);
|
|
|
|
if ( fp->f_flags & F_CDIRTY ) {
|
|
_lseek(fp->f_fd, 0L, SEEK_SET);
|
|
|
|
if (write(fp->f_fd, (PBYTE)&fp->f_control,
|
|
sizeof(Control)) != sizeof(Control))
|
|
ErrRet(DHERR_BADWRITE);
|
|
|
|
fp->f_flags &= (USHORT)~F_CDIRTY;
|
|
}
|
|
if ( fp->f_flags & F_EDIRTY )
|
|
if (flushextent(fp) == ERROR)
|
|
return ERROR;
|
|
|
|
if (close(fp->f_fd))
|
|
ErrRet(DHERR_BADWRITE);
|
|
|
|
memfree(fp->f_name);
|
|
if ( fp->f_extent != NULL )
|
|
memfree(fp->f_extent);
|
|
fp->f_flags = 0;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** getname - get a DH name for a folder
|
|
*
|
|
* Getname gets the name of an open folder. The name of a folder
|
|
* is the canonical pathname of the file that represents the folder.
|
|
*
|
|
* Entry: fh = handle to relevant folder
|
|
* Return: pointer to memalloc'ed string containing name.
|
|
*
|
|
*/
|
|
PSTR getname(Fhandle fh)
|
|
{
|
|
Folder *fp;
|
|
PSTR p;
|
|
|
|
if ( (fp = fhtofp(fh)) == NULL )
|
|
ErrRetCast(DHERR_BADFLDHAND, (PSTR));
|
|
|
|
p = _strdup(fp->f_name);
|
|
|
|
if (p == NULL) {
|
|
ErrRetCast(DHERR_NOMEM, (PSTR));
|
|
}
|
|
else
|
|
SucRet(p);
|
|
}
|
|
|
|
|
|
/** fhtofp - convert folder handle to folder pointer
|
|
*
|
|
* Outside this module, the only reference to an active folder is
|
|
* its 'folder handle.' This routine converts this handle to
|
|
* a pointer to the appropriate entry in the foldertab.
|
|
*
|
|
* Entry: fh = folder handle to convert
|
|
* Return: NULL if fh is not a valid folder handle
|
|
* pointer to Folder if fh is valid
|
|
*/
|
|
static Folder *fhtofp(Fhandle fh)
|
|
{
|
|
Folder *rv;
|
|
if ((fh < 0) || (fh >= NFOLDERS))
|
|
return NULL;
|
|
rv = &foldertab[fh];
|
|
if ( (rv->f_flags & F_BUSY) == 0)
|
|
return NULL;
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
/** index - create/manage the DH folder index
|
|
*
|
|
* DH folders contain both the data for the header and body,
|
|
* and an index that allows programs to locate that data easily.
|
|
* The index contains one entry for each document. Index
|
|
* entries are grouped together in 'extents'. Each extent contains
|
|
* a number of index entries, and pointers to the previous and
|
|
* subsequent extents. The ends of this doubly linked list marked
|
|
* by null pointers.
|
|
*
|
|
* A header block at the beginning of the file contains relevant
|
|
* information such as how many extents are allocated, how many
|
|
* documents there are, etc.
|
|
*/
|
|
|
|
|
|
/** readindex - read the index for a specific document
|
|
*
|
|
* The index entry for a specific document is read from the
|
|
* appropriate extent into the passed data area.
|
|
* We first get the appropriate extent into memory, and then
|
|
* copy the index from the extent.
|
|
*
|
|
* Entry: fp = pointer to folder that document is in
|
|
* docid = Id of document to read index for
|
|
* ip = pointer to place to read index into
|
|
*
|
|
* Return: ERROR if unsuccessful
|
|
* OK if successful
|
|
*/
|
|
static INT readindex(Folder *fp, Docid docid, Index *ip)
|
|
{
|
|
|
|
/* quick check, does doc exist? */
|
|
if ( docid > fp->f_control.c_numdoc )
|
|
ErrRet(DHERR_BADDOCID);
|
|
|
|
/* read down the extent chain forward pointers */
|
|
if (getextent(fp, (docid-1)/fp->f_control.c_nipe, 0) == ERROR)
|
|
return ERROR;
|
|
|
|
*ip = fp->f_extent->e_index[(docid - 1) % fp->f_control.c_nipe];
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/** writeindex - write the index for a specific document
|
|
*
|
|
* The index entry for a specific document is written to the
|
|
* appropriate extent from the passed data area.
|
|
* We first get the appropriate extent into memory, and then
|
|
* copy the index into the extent. The altered extent is then
|
|
* marked dirty, so that it will eventually be flushed to disk.
|
|
*
|
|
* Entry: fp = pointer to folder that document is in
|
|
* docid = Id of document to write index for
|
|
* ip = pointer to place to read index from
|
|
*
|
|
* Return: ERROR if unsuccessful
|
|
* OK if successful
|
|
*/
|
|
static INT writeindex(Folder *fp, Docid docid, Index *ip)
|
|
{
|
|
/* quick check, does doc exist? */
|
|
if ( docid > fp->f_control.c_numdoc )
|
|
ErrRet(DHERR_BADDOCID);
|
|
|
|
if (getextent(fp, (docid-1)/fp->f_control.c_nipe, 1) == ERROR)
|
|
return ERROR;
|
|
|
|
fp->f_extent->e_index[(docid - 1) % fp->f_control.c_nipe] = *ip;
|
|
fp->f_flags |= (USHORT)F_EDIRTY;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/** getextent - get a specified extent into memory
|
|
*
|
|
* The specified extent is read into memory.
|
|
* If the current extent is dirty, it must be flushed to disk.
|
|
* Then, the correct extent is located and read into memory.
|
|
* If the extent doesn't exist yet, but the flag is non-zero
|
|
* then the extent is created. New extents are created only
|
|
* if they would fall immediately after the last extent in the
|
|
* existing chain of extents.
|
|
*
|
|
* Entry: fp = pointer to folder that extent is part of
|
|
* number = extent to be read
|
|
* flag = 0 means don't create a new extent
|
|
* flag = 1 means do create a new extent if needed
|
|
*
|
|
* Return: OK if successful
|
|
* ERROR if unsuccessful
|
|
*/
|
|
static INT getextent(Folder *fp, INT number, INT flag)
|
|
{
|
|
LONG pos;
|
|
|
|
if (fp->f_extent == NULL ) {
|
|
if ((fp->f_extent=(Extent *)memalloc(fp->f_extsize)) == NULL)
|
|
ErrRet(DHERR_NOMEM);
|
|
fp->f_extnum = -1;
|
|
fp->f_extpos = 0;
|
|
}
|
|
|
|
if ( fp->f_extnum == number )
|
|
SucRet(OK);
|
|
|
|
if ( fp->f_flags & F_EDIRTY )
|
|
if (flushextent(fp) == ERROR)
|
|
return ERROR;
|
|
|
|
/* read down the links the right number of times */
|
|
if ( (fp->f_extnum > number) || (fp->f_extnum == -1) ) {
|
|
/* start at beginning */
|
|
pos = sizeof(Control);
|
|
fp->f_extnum = -1;
|
|
} else {
|
|
/* start from where we are */
|
|
pos = fp->f_extent->e_link;
|
|
}
|
|
for (; fp->f_extnum != number; pos = fp->f_extent->e_link) {
|
|
if ( pos == 0L )
|
|
break;
|
|
_lseek(fp->f_fd, pos, SEEK_SET);
|
|
|
|
if (read(fp->f_fd, (PBYTE)fp->f_extent,
|
|
fp->f_extsize) != (INT)fp->f_extsize)
|
|
ErrRet(DHERR_BADREAD);
|
|
|
|
fp->f_extnum += 1;
|
|
fp->f_extpos = pos;
|
|
}
|
|
if ( fp->f_extnum == number )
|
|
SucRet(OK);
|
|
|
|
if ( (number - fp->f_extnum) > 1 )
|
|
ErrRet(DHERR_FLDCORRUPT);
|
|
|
|
/* fprintf(stderr, "extent chain too short.\n");
|
|
* fprintf(stderr, "extnum %d number %d\n", fp->f_extnum, number);
|
|
* exit(1);
|
|
*/
|
|
|
|
if ( (number - fp->f_extnum) == 1 && flag != 0 ) {
|
|
return addextent(fp, number);
|
|
}
|
|
}
|
|
|
|
|
|
/** addextent - add a new extent to the chain
|
|
*
|
|
* A new, empty extent is added to the current chain.
|
|
* First, we get the last extent into memory. Then, we seek to the
|
|
* end of the, set the link of the last extent to that position, and
|
|
* create the new extent in memory. We flush the new extent, and
|
|
* return.
|
|
*
|
|
* Entry: fp = pointer to folder to create extent for
|
|
* number = number of extent to create.
|
|
* Return: OK if ok, ERROR otherwise
|
|
*/
|
|
static INT addextent(Folder *fp, INT number)
|
|
{
|
|
LONG pos;
|
|
|
|
if ( getextent(fp, number - 1, 0) != OK )
|
|
return ERROR;
|
|
|
|
if ( fp->f_extent->e_link != 0 ) {
|
|
/* BUGBUG */
|
|
fprintf(stderr, "Warning: trying to add extent that exists.\n");
|
|
}
|
|
pos = _lseek(fp->f_fd, 0L, SEEK_END); /* end of file */
|
|
fp->f_extent->e_link = pos;
|
|
|
|
if (flushextent(fp) == ERROR)
|
|
return ERROR;
|
|
|
|
fp->f_extpos = pos;
|
|
fp->f_extnum = number;
|
|
fp->f_flags |= (USHORT)F_EDIRTY;
|
|
|
|
clearextent(fp);
|
|
return flushextent(fp);
|
|
}
|
|
|
|
|
|
/** flushextent - flush the buffered extent for a folder
|
|
*
|
|
* The buffered extent for the folder is written to the disk file
|
|
* in the appropriate location.
|
|
*
|
|
* Entry: fp = pointer to relevant folder structure
|
|
* Exit: OK = it worked
|
|
* ERROR = some problem arose
|
|
*/
|
|
static INT flushextent(Folder *fp)
|
|
{
|
|
_lseek(fp->f_fd, fp->f_extpos, SEEK_SET);
|
|
if (write(fp->f_fd, (PBYTE)fp->f_extent,
|
|
fp->f_extsize) != (INT)fp->f_extsize)
|
|
ErrRet(DHERR_BADWRITE);
|
|
fp->f_flags &= (USHORT)~F_EDIRTY;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/** clearextent - clear an extent
|
|
*
|
|
* The buffered extent for the relevent folder is cleared
|
|
* to zeros.
|
|
*
|
|
* Entry: fp = ptr to relevant folder
|
|
*/
|
|
static VOID clearextent(Folder *fp)
|
|
{
|
|
register Index *ip;
|
|
|
|
for (ip = fp->f_extent->e_index;
|
|
ip < &(fp->f_extent->e_index[fp->f_control.c_nipe]);
|
|
ip += 1) {
|
|
ip->i_hpos = ip->i_bpos = 0;
|
|
ip->i_hlen = ip->i_blen = 0;
|
|
ip->i_flags = 0;
|
|
}
|
|
fp->f_extent->e_link = 0;
|
|
}
|
|
|
|
|
|
/*** getdoc - get a document
|
|
*
|
|
* get a document handle for the document indicated by flags [and docid].
|
|
* flags can take one of the following values:
|
|
* DOC_SPEC document specified by 'docid' in 'folder'
|
|
* DOC_CREATE create new document in 'folder'
|
|
*
|
|
* Entry: fh = Fhandle to parent folder of document
|
|
* func = desired action
|
|
* docid = document id, if needed
|
|
* Return: ERROR if failed, Dhandle to document if successful
|
|
*/
|
|
Dhandle getdoc(Fhandle fh, SHORT func, Docid docid)
|
|
{
|
|
Folder *fp;
|
|
Document *dp;
|
|
LONG fileEnd;
|
|
|
|
if ( (fp = fhtofp(fh)) == NULL )
|
|
ErrRetCast(DHERR_BADFLDHAND,(Dhandle));
|
|
|
|
/* find empty document slot */
|
|
for (dp = doctab; dp < &doctab[NDOCS]; dp += 1) {
|
|
if (dp->d_flags == 0)
|
|
break;
|
|
}
|
|
if (dp >= &doctab[NDOCS])
|
|
ErrRetCast(DHERR_TOOMANYDOCS,(Dhandle));
|
|
|
|
/* do function specific work */
|
|
switch (func) {
|
|
|
|
case DOC_SPEC: /* find a specific document */
|
|
|
|
/* quick check, does doc exist? */
|
|
if ( docid > fp->f_control.c_numdoc )
|
|
ErrRetCast(DHERR_BADDOCID,(Dhandle));
|
|
|
|
if ( readindex(fp, docid, &dp->d_index) == ERROR )
|
|
return (Dhandle)ERROR;
|
|
|
|
/* make sure the document exists */
|
|
if ( (dp->d_index.i_flags & DAF_EXISTS) != DAF_EXISTS )
|
|
ErrRetCast(DHERR_BADDOCID,(Dhandle));
|
|
if (-1L == (fileEnd = filelength (fp->f_fd)))
|
|
ErrRetCast(DHERR_BADREAD,(Dhandle));
|
|
if ( ( (dp->d_index.i_hpos + dp->d_index.i_hlen) < 0 ) ||
|
|
( (dp->d_index.i_bpos + dp->d_index.i_blen) < 0 ) ||
|
|
( (dp->d_index.i_hpos + dp->d_index.i_hlen) > fileEnd ) ||
|
|
( (dp->d_index.i_bpos + dp->d_index.i_blen) > fileEnd ) )
|
|
ErrRetCast(DHERR_FLDCORRUPT, (Dhandle));
|
|
|
|
/* ok, init the document struct and return */
|
|
dp->d_docid = docid;
|
|
dp->d_flags = 0;
|
|
break;
|
|
|
|
case DOC_CREATE: /* make a new document */
|
|
/* get a new document id */
|
|
fp->f_control.c_numdoc += 1;
|
|
dp->d_docid = fp->f_control.c_numdoc;
|
|
fp->f_flags |= (USHORT)F_CDIRTY;
|
|
|
|
/* set up an empty document */
|
|
dp->d_index.i_flags = DAF_EXISTS;
|
|
dp->d_index.i_hlen = 0;
|
|
dp->d_index.i_blen = 0;
|
|
dp->d_index.i_hpos = 0;
|
|
dp->d_index.i_bpos = 0;
|
|
dp->d_flags |= (USHORT)D_IDIRTY;
|
|
break;
|
|
|
|
default: /* Bad function */
|
|
ErrRet(DHERR_BADFUNC);
|
|
}
|
|
|
|
/* do work common to all funcs */
|
|
|
|
dp->d_flags |= (USHORT)D_BUSY;
|
|
dp->d_fp = fp;
|
|
dp->d_brpos = 0L;
|
|
SucRet((Dhandle)(dp - doctab));
|
|
}
|
|
|
|
|
|
/*** removedoc - backout last doc created from getdoc
|
|
*
|
|
* removes last document added. index entries are not
|
|
* reinitialized, body/header not deleted. this is for
|
|
* error recovery from failed puttext or putdoc operations.
|
|
*
|
|
* Entry: fh = Fhandle to parent folder of document
|
|
* Return: ERROR if failed
|
|
*/
|
|
INT removedoc(Fhandle fh)
|
|
{
|
|
Folder *fp;
|
|
|
|
if ( (fp = fhtofp(fh)) == NULL )
|
|
ErrRetCast(DHERR_BADFLDHAND,(Dhandle));
|
|
fp->f_control.c_numdoc -= 1;
|
|
fp->f_flags |= (USHORT)F_CDIRTY;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** scanfolder - scan the existing documents
|
|
*
|
|
* return the document id for the specified document
|
|
* func can take one of the following values:
|
|
* DOC_SET prepare for scan, return first doc in scan
|
|
* DOC_NEXT return next document in scan
|
|
*
|
|
* Entry: fh = Fhandle to parent folder of document
|
|
* func = desired action
|
|
* docid = document id
|
|
* Return: ERROR if failed, docid of document if successful
|
|
*/
|
|
Docid scanfolder(Fhandle fh, SHORT func, Docid id)
|
|
{
|
|
Folder *fp;
|
|
SHORT i;
|
|
LONG flags;
|
|
|
|
if ( (fp = fhtofp(fh)) == NULL )
|
|
ErrRetCast(DHERR_BADFLDHAND,(Docid));
|
|
|
|
/* do function specific work */
|
|
switch (func) {
|
|
|
|
case DOC_SET: /* set the first document for scanning */
|
|
fp->f_sdocid = id;
|
|
/* fall thru */
|
|
|
|
case DOC_NEXT: /* find the next document */
|
|
while ( 1 ) {
|
|
if ( fp->f_sdocid > fp->f_control.c_numdoc )
|
|
ErrRetCast(DHERR_BADDOCHAND,(Docid));
|
|
if ( getextent(fp,
|
|
(fp->f_sdocid - 1)/fp->f_control.c_nipe,0) != OK)
|
|
return ERROR;
|
|
i = (SHORT)(fp->f_sdocid - 1) % fp->f_control.c_nipe;
|
|
flags = fp->f_extent->e_index[i].i_flags;
|
|
if ( (flags & DAF_EXISTS) == DAF_EXISTS )
|
|
SucRet(fp->f_sdocid++);
|
|
fp->f_sdocid += 1;
|
|
}
|
|
|
|
default: /* Bad function */
|
|
ErrRet(DHERR_BADFUNC);
|
|
}
|
|
}
|
|
|
|
|
|
/*** getfldlen - return upper bound on last docid currently in folder
|
|
*
|
|
* Entry: fh = handle to folder of interest
|
|
* Return: Highest docid currently possible in folder, or ERROR.
|
|
*/
|
|
Docid getfldlen(Fhandle fh)
|
|
{
|
|
Folder *fp;
|
|
|
|
if ( (fp = fhtofp(fh)) == NULL )
|
|
ErrRetCast(DHERR_BADFLDHAND,(Docid));
|
|
SucRet(fp->f_control.c_numdoc);
|
|
}
|
|
|
|
|
|
/*** putdoc - close a document
|
|
*
|
|
* Put_doc indicates that the document is no longer going to
|
|
* be manipulated. Thus, we can free all the resources that
|
|
* are allocated for that document.
|
|
*
|
|
* Entry: dh = Dhandle to document to close
|
|
* Return: none unless there's an error, in which case ERROR
|
|
*/
|
|
INT putdoc(Dhandle dh)
|
|
{
|
|
Document *dp;
|
|
INT rc = OK;
|
|
|
|
if ((dp = dhtodp(dh)) == NULL)
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
if ( dp->d_flags & D_IDIRTY)
|
|
rc = writeindex(dp->d_fp, dp->d_docid, &(dp->d_index));
|
|
dp->d_fp->f_cnt -= 1;
|
|
dp->d_flags = 0;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*** deldoc - make a document go away
|
|
*
|
|
* deldoc simply calls getdoc and putflags to set DAF_DELETED.
|
|
* deldoc will go away.
|
|
*
|
|
* BUGBUG - deleting an open document will result in chaos
|
|
*/
|
|
INT deldoc(Fhandle fh, Docid docid)
|
|
{
|
|
Dhandle dh;
|
|
if ((dh = getdoc(fh, DOC_SPEC, docid)) == ERROR)
|
|
return ERROR;
|
|
if (putflags(dh, DAF_DELETED, 0xffffffffL) == ERROR)
|
|
return ERROR;
|
|
return putdoc(dh);
|
|
}
|
|
|
|
|
|
/*** getflags - return the attribute flags for a document
|
|
*
|
|
* Getflags returns the flag set for a document.
|
|
* Only flag bits which are defined for use by applications can
|
|
* be returned. The mask argument selects which flags to report
|
|
* on. Using a mask of -1L will report on all user manipulable
|
|
* flags. Using a mask of DAT_DELETED, for example, will indicate
|
|
* whether the given document is deleted. Applications are advised
|
|
* to set mask to only report those bits they understand, since new
|
|
* bits may be defined in the future.
|
|
*
|
|
*
|
|
* Note: It is NOT an error to request the status of undefined
|
|
* flags, it simply isn't allowed. No warning is given.
|
|
*
|
|
* Entry: dh = handle to document of interest
|
|
* mask = one bits select which flags will be reported
|
|
* flags = word to fill in with flags
|
|
*
|
|
* Exit: flags = relevent part of flags word for document
|
|
*
|
|
* Return: OK or ERROR
|
|
*/
|
|
INT getflags(Dhandle dh, Docflag mask, Docflag *flags)
|
|
{
|
|
Document *dp;
|
|
|
|
if ( (dp = dhtodp(dh)) == NULL)
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
/* turn off bits that user isn't allowed to manipulate */
|
|
mask &= DAF_NOTRESERVED;
|
|
*flags = dp->d_index.i_flags & mask;
|
|
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** putflags - set the attribute flags for a document
|
|
*
|
|
* Putflags sets the flag set for a document.
|
|
* Only flag bits which are defined for use by applications can
|
|
* be set. The mask argument selects which flags to set.
|
|
* Using a mask of -1L will cause all user manipulable flags to
|
|
* to be set. Using a mask of DAT_DELETE, for example, will set
|
|
* or clear the document's deleted bit. Applications are warned
|
|
* to set mask to only those bits they understand, since new
|
|
* bits may be defined in the future, and setting such bits will
|
|
* usually have unexpected results.
|
|
*
|
|
* Note: It is NOT an error to request the status of undefined
|
|
* flags, it simply isn't allowed. No warning is given.
|
|
*
|
|
* Note: New flag settings don't take effect until a putdoc is done.
|
|
*
|
|
* Entry: dh = handle to document of interest
|
|
* mask = one bits select which flags will be reported
|
|
* flags = values to set flags to
|
|
*
|
|
* Return: OK or ERROR
|
|
*/
|
|
INT putflags(Dhandle dh, Docflag mask, Docflag flags)
|
|
{
|
|
Document *dp;
|
|
|
|
if ( (dp = dhtodp(dh)) == NULL)
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
mask &= DAF_NOTRESERVED; /* turn off bits user can't set */
|
|
flags &= mask; /* turn off user's don't care bits */
|
|
dp->d_index.i_flags &= ~mask; /* clear target bits */
|
|
dp->d_index.i_flags |= flags; /* or user values into target bits */
|
|
dp->d_flags |= (USHORT)D_IDIRTY;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** gettext - write document to stream
|
|
*
|
|
* The entire document, header and all, is written to the
|
|
* passed stream in presentation format.
|
|
*
|
|
* Entry: dh = handle of document to write to stream
|
|
* stream = stream to write document onto
|
|
*
|
|
* Return: ERROR or OK
|
|
*/
|
|
INT gettext(Dhandle dh, INT file)
|
|
{
|
|
Document *dp;
|
|
PSTR cp;
|
|
UINT lcp;
|
|
|
|
if ((dp = dhtodp(dh)) == NULL)
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
/* write out header */
|
|
if ( (cp = gethdr(dh)) == (PSTR)ERROR )
|
|
return ERROR;
|
|
|
|
lcp = strlen(cp);
|
|
if (write(file, cp, lcp) != (INT)lcp)
|
|
ErrRetCu(DHERR_BADWRITE, memfree(cp));
|
|
memfree(cp);
|
|
if (write(file, "\n", 1) != 1)
|
|
ErrRet(DHERR_BADWRITE);
|
|
|
|
/* write out body */
|
|
return getbdy(dh, file);
|
|
}
|
|
|
|
|
|
/*** gethdr - get header block
|
|
*
|
|
* We return a pointer to on allocated piece of memory that
|
|
* contains the header for the document. The memory is allocated
|
|
* from the heap. Because the memory pointer is returned to the
|
|
* user, we must assume that we transfer ownership to the caller.
|
|
* Thus, he is responsible for freeing the memory when he no longer
|
|
* needs it.
|
|
*
|
|
* Entry: dh = handle of relevant document
|
|
*
|
|
* Return: pointer to block of memory if successful
|
|
* ERROR if unsuccessful
|
|
*/
|
|
PSTR gethdr(Dhandle dh)
|
|
{
|
|
Document *dp;
|
|
PSTR cp;
|
|
INT size;
|
|
|
|
if ( (dp = dhtodp(dh)) == NULL)
|
|
ErrRetCast(DHERR_BADDOCHAND, (PSTR));
|
|
|
|
size = (INT)dp->d_index.i_hlen;
|
|
if ((cp = (PSTR)memalloc((size + 1)*sizeof(CHAR))) == NULL)
|
|
ErrRetCast(DHERR_NOMEM, (PSTR));
|
|
_lseek(dp->d_fp->f_fd, dp->d_index.i_hpos, SEEK_SET);
|
|
if (read(dp->d_fp->f_fd, (PBYTE)cp, size) != size)
|
|
ErrRetCuCast(DHERR_BADREAD, memfree(cp), (PSTR));
|
|
cp[size] = '\0';
|
|
SucRet(cp);
|
|
}
|
|
|
|
|
|
/*** getbdy - write the body of the document to the passed file handle
|
|
*
|
|
* The body of the relevant document is written onto the passed
|
|
* file handle.
|
|
*
|
|
* Entry: dh = handle of document
|
|
* file = file handle to write body onto
|
|
*
|
|
* Return: OK or ERROR
|
|
*/
|
|
INT getbdy(Dhandle dh, INT file)
|
|
{
|
|
Document *dp;
|
|
LONG pos, length;
|
|
INT fd;
|
|
PBYTE bufp;
|
|
INT cnt;
|
|
|
|
if ( (dp = dhtodp(dh)) == NULL )
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
if ( (bufp = (PBYTE)memalloc(BUFFERSIZE)) == NULL )
|
|
ErrRet(DHERR_NOMEM);
|
|
|
|
length = dp->d_index.i_blen;
|
|
pos = dp->d_index.i_bpos;
|
|
fd = dp->d_fp->f_fd;
|
|
|
|
_lseek(fd, pos, SEEK_SET);
|
|
|
|
while ( length > 0 ) {
|
|
if ( length > BUFFERSIZE )
|
|
cnt = BUFFERSIZE;
|
|
else
|
|
cnt = (INT)length;
|
|
cnt = read(fd, bufp, cnt);
|
|
if ((cnt == -1) || (cnt == 0))
|
|
ErrRetCu(DHERR_BADREAD, memfree(bufp));
|
|
if (write(file, bufp, cnt) != cnt)
|
|
ErrRetCu(DHERR_BADWRITE, memfree(bufp));
|
|
length -= (long)cnt;
|
|
}
|
|
memfree(bufp);
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** puttext - replace contents of a document
|
|
*
|
|
* The entire document is replaced by the document read from
|
|
* the stream, assumed to be a document is presentation format
|
|
*
|
|
* Entry: dh = handle of document to replace
|
|
* file = file handle to read new document content from
|
|
*
|
|
* Return: ERROR or OK
|
|
*/
|
|
INT puttext(Dhandle dh, INT file)
|
|
{
|
|
Document *dp;
|
|
PBYTE bufp;
|
|
PBYTE wp;
|
|
INT cnt;
|
|
INT nllast = 0;
|
|
INT fd;
|
|
|
|
if ( (dp = dhtodp(dh)) == NULL )
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
if ( (bufp = (PBYTE)memalloc(BUFFERSIZE+1)) == NULL )
|
|
ErrRet(DHERR_NOMEM);
|
|
|
|
fd = dp->d_fp->f_fd;
|
|
dp->d_index.i_hpos = _lseek(fd, 0L, SEEK_END);
|
|
dp->d_index.i_hlen = 0;
|
|
|
|
/* read in header */
|
|
while ( 1 ) {
|
|
if ( (cnt = read(file, bufp, BUFFERSIZE)) == 0 )
|
|
break;
|
|
|
|
if (cnt == -1)
|
|
ErrRetCu(DHERR_BADREAD, memfree(bufp));
|
|
|
|
if ( nllast && *bufp == '\n' ) {
|
|
wp = bufp + sizeof(CHAR);
|
|
break;
|
|
}
|
|
bufp[cnt] = '\0';
|
|
|
|
if ( (wp = (PBYTE)blankline((PSTR)bufp)) != NULL ) {
|
|
/* found end of header */
|
|
wp += sizeof(CHAR);
|
|
if (write(fd, bufp, wp - bufp) != wp - bufp)
|
|
ErrRetCu(DHERR_BADWRITE, memfree(bufp));
|
|
dp->d_index.i_hlen += (LONG)(wp - bufp);
|
|
wp = (PBYTE)endbline((PSTR)wp);
|
|
break;
|
|
}
|
|
if ( bufp[cnt - 1] == '\n' )
|
|
nllast = 1;
|
|
if (write(fd, bufp, cnt) != cnt)
|
|
ErrRetCu(DHERR_BADWRITE, memfree(bufp));
|
|
dp->d_index.i_hlen += (LONG)cnt;
|
|
}
|
|
|
|
/*
|
|
* wp = NULL iff there is no body
|
|
* wp = ptr to start of body in buffer iff there is a body
|
|
* bufp = ptr to buffer
|
|
* cnt = amount of data in buffer
|
|
*/
|
|
if ( wp == NULL ) {
|
|
dp->d_index.i_bpos = dp->d_index.i_hpos;
|
|
dp->d_index.i_blen = dp->d_index.i_hlen;
|
|
dp->d_index.i_hpos = 0;
|
|
dp->d_index.i_hlen = 0;
|
|
} else {
|
|
dp->d_index.i_bpos = dp->d_index.i_hpos + dp->d_index.i_hlen;
|
|
dp->d_index.i_blen = (LONG)(cnt - (INT)(wp - bufp));
|
|
cnt = cnt - (INT)(wp - bufp);
|
|
if (write(fd, wp, cnt) != cnt)
|
|
ErrRetCu(DHERR_BADWRITE, memfree(bufp));
|
|
}
|
|
|
|
/*
|
|
* write remainder of file into body
|
|
*/
|
|
while ( (cnt = read(file, bufp, BUFFERSIZE)) != 0 ) {
|
|
if (cnt == ERROR)
|
|
ErrRetCu(DHERR_BADREAD, memfree(bufp));
|
|
dp->d_index.i_blen += (LONG)cnt;
|
|
if (write(fd, bufp, cnt) != cnt)
|
|
ErrRetCu(DHERR_BADWRITE, memfree(bufp));
|
|
}
|
|
dp->d_flags |= (USHORT)D_IDIRTY;
|
|
memfree(bufp);
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** putbdy - replace the body of a document
|
|
*
|
|
* The body of the specified document is replaced with the
|
|
* body read from the specified file descriptor.
|
|
*
|
|
* Note - this code is arranged so that if we punt due to a write
|
|
* failure, the old version of the document will be left
|
|
* intact. Further, we will resize the file to it's previous
|
|
* size, and thus avoid filling the disk with trash.
|
|
*
|
|
* ENTRY dh = handle of document to replace
|
|
* ifd = file handle to read body from
|
|
*
|
|
* RETURN ERROR if some difficulty arises, OK if it worked.
|
|
*
|
|
*/
|
|
INT putbdy(Dhandle dh, INT ifd)
|
|
{
|
|
Document *dp;
|
|
INT fd;
|
|
INT cnt;
|
|
INT rc = OK;
|
|
PBYTE bufp;
|
|
LONG newpos, sumcount;
|
|
|
|
if ( (dp = dhtodp(dh)) == NULL )
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
if ( (bufp = (PBYTE)memalloc(BUFFERSIZE)) == NULL )
|
|
ErrRet(DHERR_NOMEM);
|
|
|
|
fd = dp->d_fp->f_fd;
|
|
|
|
newpos = _lseek(fd, 0L, SEEK_END);
|
|
sumcount = 0;
|
|
|
|
while ( (cnt = read(ifd, bufp, BUFFERSIZE)) != 0 ) {
|
|
if (cnt == -1) {
|
|
_chsize(fd, newpos);
|
|
ErrRetCu(DHERR_BADREAD, memfree(bufp));
|
|
}
|
|
sumcount += (LONG)cnt;
|
|
if (write(fd, bufp, cnt) != cnt) {
|
|
_chsize(fd, newpos);
|
|
ErrRetCu(DHERR_BADWRITE, memfree(bufp));
|
|
}
|
|
}
|
|
|
|
dp->d_index.i_bpos = newpos;
|
|
dp->d_index.i_blen = sumcount;
|
|
dp->d_flags |= (USHORT)D_IDIRTY;
|
|
|
|
memfree(bufp);
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** puthdr - set new header block
|
|
*
|
|
* The document specified by dh gets a new header,
|
|
* which is specified by the memory block that bp points to.
|
|
* The header block is NULL terminated byte string.
|
|
*
|
|
* If new header is <= old header, write it back in place
|
|
* to avoid wasting lots of space with redundent header copies.
|
|
* Don't update in-memory structures until after write works,
|
|
* so that if it fails, things will be as correct as possible.
|
|
*
|
|
* ENTRY dh = handle of document which will recieve the new header
|
|
* bp = pointer to new header block
|
|
*
|
|
* RETURN ERROR if some problem, else OK.
|
|
*
|
|
* #ERROR# Should we restore old header if write fails?
|
|
* Mark entry corrupt?
|
|
*/
|
|
INT puthdr(Dhandle dh, PSTR bp)
|
|
{
|
|
Document *dp;
|
|
LONG bl, newpos;
|
|
|
|
if ( (dp = dhtodp(dh)) == NULL )
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
bl = (LONG)strlen(bp);
|
|
|
|
if (bl <= dp->d_index.i_hlen) {
|
|
|
|
/* Header will fit in prior space, write it back. If
|
|
this write fails, user is hosed, tough, can't afford
|
|
to restore header every time it's touched. */
|
|
|
|
_lseek(dp->d_fp->f_fd, dp->d_index.i_hpos, SEEK_SET);
|
|
if ( write(dp->d_fp->f_fd, (PBYTE)bp, (INT)bl) != (INT)bl)
|
|
ErrRet(DHERR_BADWRITE);
|
|
}
|
|
else {
|
|
newpos = _lseek(dp->d_fp->f_fd, 0L, SEEK_END);
|
|
|
|
if (write(dp->d_fp->f_fd, (PBYTE)bp, (INT)bl) != (INT)bl) {
|
|
_chsize(dp->d_fp->f_fd, newpos);
|
|
ErrRet(DHERR_BADWRITE);
|
|
}
|
|
dp->d_index.i_hpos = newpos;
|
|
}
|
|
dp->d_index.i_hlen = bl;
|
|
dp->d_flags |= (USHORT)D_IDIRTY;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** getid - get the name of a document
|
|
*
|
|
* The name of the document is its 'document id', really
|
|
* just a number. This number is computed based on information
|
|
* in the doclist and returned. (ie Convert a document handle -> doc id)
|
|
*
|
|
* ENTRY dh = handle of document in question
|
|
*
|
|
* RETURN id of document, or error
|
|
*/
|
|
Docid getid(Dhandle dh)
|
|
{
|
|
Document *dp;
|
|
|
|
if ((dp = dhtodp(dh)) == NULL)
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
SucRet(dp->d_docid);
|
|
}
|
|
|
|
|
|
/*** gethdrlen - get the length of a document's header
|
|
*
|
|
* The length of the header is retrieved from the
|
|
* index, and returned.
|
|
*
|
|
* ENTRY dh = handle of document in question
|
|
*
|
|
* RETURN length of document's header, or ERROR
|
|
*/
|
|
LONG gethdrlen(Dhandle dh)
|
|
{
|
|
Document *dp;
|
|
|
|
if ((dp = dhtodp(dh)) == NULL)
|
|
ErrRetCast(DHERR_BADDOCHAND,(LONG));
|
|
|
|
SucRet(dp->d_index.i_hlen);
|
|
}
|
|
|
|
|
|
/*** getbdylen - get the length of a document's body
|
|
*
|
|
* The length of the body is retrieved from the
|
|
* index, and returned.
|
|
*
|
|
* ENTRY dh = handle of document in question
|
|
*
|
|
* RETURN length of document's body, or ERROR
|
|
*/
|
|
LONG getbdylen(Dhandle dh)
|
|
{
|
|
Document *dp;
|
|
|
|
if ((dp = dhtodp(dh)) == NULL)
|
|
ErrRetCast(DHERR_BADDOCHAND,(LONG));
|
|
|
|
SucRet(dp->d_index.i_blen);
|
|
}
|
|
|
|
|
|
/*** readbdy - read data from body into user buffer
|
|
*
|
|
* readbdy does a "read" on the body of document. (Use gethdr to get
|
|
* the header of a document.)
|
|
*
|
|
* ENTRY dh - document handle for document of interest
|
|
* bp - buffer pointer, points to where data should go
|
|
* want - how many bytes to read
|
|
*
|
|
* EXIT got - how many bytes actually read, 0 -> EOF
|
|
*
|
|
* RETURN OK if things worked (got == 0 for EOF),
|
|
* ERROR if some error arose
|
|
*/
|
|
INT readbdy(Dhandle dh, PBYTE bp, UINT want, PUINT got)
|
|
{
|
|
Document *dp;
|
|
LONG left;
|
|
LONG pos;
|
|
INT fd;
|
|
|
|
|
|
if ((dp = dhtodp(dh)) == NULL)
|
|
ErrRetCu(DHERR_BADDOCHAND, *got = 0);
|
|
|
|
if (dp->d_brpos >= dp->d_index.i_blen) {
|
|
*got = 0;
|
|
SucRet(OK);
|
|
}
|
|
|
|
pos = dp->d_brpos;
|
|
fd = dp->d_fp->f_fd;
|
|
|
|
left = dp->d_index.i_blen - pos;
|
|
if ((LONG)want > left) want = (UINT)left;
|
|
|
|
_lseek(fd, dp->d_index.i_bpos+pos, SEEK_SET);
|
|
if (read(fd, bp, want) != (INT)want)
|
|
ErrRetCu(DHERR_BADREAD, *got = 0);
|
|
*got = want;
|
|
dp->d_brpos = dp->d_brpos + want;
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/*** seekbdy - set address readbdy will read from
|
|
*
|
|
* seekbdy sets the position within the document's body that the next
|
|
* readbdy call on that document will start reading from. First byte
|
|
* is number 0, implict seekbdy(dh, 0L) is done at getdoc() time.
|
|
*
|
|
* seekbdy will also return the current read position. If fun = 1, it
|
|
* does nothing else.
|
|
*
|
|
* Seeking beyond the end of a body produces an error. Use getbdylen to
|
|
* learn the length of a document's body.
|
|
*
|
|
* ENTRY dh - document handle for document of interest
|
|
* fun - function 0 -> set position, 1 -> return current position
|
|
* rpos - new position to set if fun = 0
|
|
*
|
|
* EXIT oldrpos - previous position
|
|
*
|
|
* RETURN ERROR or OK
|
|
*/
|
|
INT seekbdy(Dhandle dh, SHORT fun, LONG rpos, PLONG oldrpos)
|
|
{
|
|
Document *dp;
|
|
|
|
if ((dp = dhtodp(dh)) == NULL)
|
|
ErrRet(DHERR_BADDOCHAND);
|
|
|
|
if (rpos >= dp->d_index.i_blen)
|
|
ErrRet(DHERR_BADSEEK);
|
|
|
|
*oldrpos = dp->d_brpos;
|
|
if (fun == 0)
|
|
dp->d_brpos = rpos;
|
|
|
|
SucRet(OK);
|
|
}
|
|
|
|
|
|
/** dhtodp - convert document handle to pointer to document list structure
|
|
*
|
|
* Dhtodp checks to make sure that dh is a valid handle, and if it is,
|
|
* returns the internal document structure that it corresponds to. If not,
|
|
* NULL will be returned, indicating an error.
|
|
*
|
|
* ENTRY dh - document handle to test and dereference
|
|
*
|
|
* RETURN pointer to docstrc of interest, or null if error
|
|
*
|
|
* GLOBALS doctab
|
|
*/
|
|
static Document *dhtodp(Dhandle dh)
|
|
{
|
|
/* handle ok? */
|
|
if ( ((doctab[dh].d_flags & D_BUSY) == 0) || (dh >= NDOCS) || (dh < 0))
|
|
return NULL;
|
|
return &doctab[dh];
|
|
}
|
|
|
|
|
|
/** blankline - search for blank line - report beginning
|
|
*
|
|
* A blank line is a sequence of 0 or more blanks or tabs or CR's between
|
|
* newlines. (This definition is more robust that the previous "two
|
|
* newlines in a row" approach.) To find the "end-of-line" of the blank
|
|
* line whose address we return, the caller should call endbline.
|
|
*
|
|
* ENTRY cp = ptr to place to start search.
|
|
*
|
|
* RETURN ptr to newline preceeding blank line, if found, else NULL.
|
|
*/
|
|
static PSTR blankline(PSTR cp)
|
|
{
|
|
PSTR ep;
|
|
|
|
while ( (cp = strchr(cp, '\n')) != NULL ) {
|
|
ep = strspn(cp+1, " \t\r") + cp + 1;
|
|
if (*ep == '\n')
|
|
return cp;
|
|
if (*ep == '\0')
|
|
return NULL;
|
|
cp = ep + 1;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/** endbline - report end of blank line
|
|
*
|
|
* See: blankline
|
|
*
|
|
* Endbline scans over the blanks, tabs and carriage returns that
|
|
* make up a blank line, and returns a pointer to the character
|
|
* AFTER the newline that terminates a blank line.
|
|
*
|
|
* WARNING endbline assumes it's called on a "correct" string, which
|
|
* is ensured by calling blankline above.
|
|
*
|
|
* ENTRY cp = ptr to character AFTER newline that preceeds blank line.
|
|
* (ie return from blankline() + 1 )
|
|
*
|
|
* RETURN ptr to character AFTER newline that ends blank line
|
|
*/
|
|
static PSTR endbline(PSTR cp)
|
|
{
|
|
cp = strspn(cp, " \t\r") + cp;
|
|
|
|
/* We assume that *cp == trailing '\n', found by blankline */
|
|
|
|
cp++; /* *cp == whatever follows the blank line */
|
|
|
|
return cp;
|
|
}
|