mirror of
https://github.com/open-simh/simtools.git
synced 2026-01-25 19:56:30 +00:00
Too much to list all, but includes (in no particular order): - Cleanup for 64-bit builds, MSVC warnings. - Structured help - Help file compiler. - Supports volsets, writes/create work. - Support for I18n in messages, help. - Makefiles. - Initialize volume/volset - Command line editing/history Builds and works on Linux and Windows (VS). Not recently built or tested on other platforms, but not intentinonally broken.
1200 lines
42 KiB
C
1200 lines
42 KiB
C
/* Direct.c */
|
|
|
|
/*
|
|
* This is part of ODS2 written by Paul Nankervis,
|
|
* email address: Paulnank@au1.ibm.com
|
|
*
|
|
* ODS2 is distributed freely for all members of the
|
|
* VMS community to use. However all derived works
|
|
* must maintain comments in their source to acknowledge
|
|
* the contributions of the original author and
|
|
* subsequent contributors. This is free software; no
|
|
* warranty is offered, and while we believe it to be useful,
|
|
* you use it at your own risk.
|
|
*/
|
|
|
|
/* This module does all directory file handling - mostly
|
|
* lookups of filenames in directory files...
|
|
*/
|
|
|
|
#if !defined( DEBUG ) && defined( DEBUG_DIRECT )
|
|
#define DEBUG DEBUG_DIRECT
|
|
#else
|
|
#ifndef DEBUG
|
|
#define DEBUG 0
|
|
#endif
|
|
#endif
|
|
|
|
#define DEBUGx on
|
|
|
|
#include <ctype.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "access.h"
|
|
#include "descrip.h"
|
|
#include "direct.h"
|
|
#include "fibdef.h"
|
|
#include "ods2.h"
|
|
#include "ssdef.h"
|
|
#include "stsdef.h"
|
|
#include "sysmsg.h"
|
|
|
|
#define BLOCKSIZE 512
|
|
#define MAXREC (BLOCKSIZE - 2)
|
|
|
|
/* Some statistical counters... */
|
|
|
|
static int direct_lookups = 0;
|
|
static int direct_searches = 0;
|
|
static int direct_deletes = 0;
|
|
static int direct_inserts = 0;
|
|
static int direct_splits = 0;
|
|
static int direct_checks = 0;
|
|
static int direct_matches = 0;
|
|
|
|
/************************************************************** direct_show() */
|
|
|
|
/* direct_show - to print directory statistics */
|
|
|
|
void direct_show( void ) {
|
|
printf( "DIRECT_SHOW Lookups: %d ", direct_lookups );
|
|
printf( "Searches: %d ", direct_searches );
|
|
printf( "Deletes: %d ", direct_deletes );
|
|
printf( "Inserts: %d ", direct_inserts );
|
|
printf( "Splits: %d\n", direct_splits );
|
|
}
|
|
|
|
/*************************************************************** name_check() */
|
|
|
|
/* name_check() - take a name specification and return name length without
|
|
the version number, an integer version number, and a wildcard flag */
|
|
|
|
static vmscond_t name_check( char *str, int len, int *retlen,
|
|
int *retver, int *wildflag ) {
|
|
int wildcard = FALSE;
|
|
char *name_start = str;
|
|
register int dots = 0;
|
|
register char *name = name_start;
|
|
register char *name_end = name + len;
|
|
direct_checks++;
|
|
|
|
/* Go through the specification checking for illegal characters */
|
|
|
|
while (name < name_end) {
|
|
register char ch = *name++;
|
|
if (ch == '.') {
|
|
if ((name - name_start) > 40)
|
|
return SS$_BADFILENAME;
|
|
name_start = name;
|
|
if (dots++ > 1)
|
|
break;
|
|
} else {
|
|
if (ch == ';') {
|
|
break;
|
|
} else {
|
|
if (ch == '*' || ch == '%') {
|
|
wildcard = TRUE;
|
|
} else {
|
|
if (ch == '[' || ch == ']' || ch == ':' ||
|
|
!isprint(ch))
|
|
return SS$_BADFILENAME;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ((name - name_start) > 40)
|
|
return SS$_BADFILENAME;
|
|
|
|
/* Return the name length and start checking the version */
|
|
|
|
*retlen = (int)(name - str - 1);
|
|
dots = 0;
|
|
if (name < name_end) {
|
|
register char ch = *name;
|
|
if (ch == '*') {
|
|
if (++name < name_end)
|
|
return SS$_BADFILENAME;
|
|
dots = 32768; /* Wildcard representation of version! */
|
|
wildcard = TRUE;
|
|
} else {
|
|
register int sign = 1;
|
|
if (ch == '-') {
|
|
name++;
|
|
sign = -1;
|
|
}
|
|
while (name < name_end) {
|
|
ch = *name++;
|
|
if (!isdigit(ch))
|
|
return SS$_BADFILENAME;
|
|
dots = dots * 10 + (ch - '0');
|
|
}
|
|
dots *= sign;
|
|
}
|
|
}
|
|
*retver = dots;
|
|
*wildflag = wildcard;
|
|
return SS$_NORMAL;
|
|
}
|
|
|
|
/*************************************************************** name_match() */
|
|
|
|
#define MAT_LT 0
|
|
#define MAT_EQ 1
|
|
#define MAT_GT 2
|
|
#define MAT_NE 3
|
|
|
|
/* name_match() - compare a name specification with a directory entry
|
|
and determine if there is a match, too big, too small... */
|
|
|
|
int name_match(char *spec, int spec_len, char *dirent, int dirent_len) {
|
|
int percent = MAT_GT;
|
|
register char *name = spec, *entry = dirent;
|
|
register char *name_end = name + spec_len, *entry_end = entry + dirent_len;
|
|
direct_matches++;
|
|
|
|
/* See how much name matches without wildcards... */
|
|
|
|
while (name < name_end && entry < entry_end) {
|
|
register char sch = *name;
|
|
if (sch != '*') {
|
|
register char ech = *entry;
|
|
if (sch != ech) {
|
|
if (toupper(sch) != toupper(ech)) {
|
|
if (sch == '%') {
|
|
percent = MAT_NE;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
name++;
|
|
entry++;
|
|
}
|
|
|
|
/* Mismatch - return result unless wildcard... */
|
|
|
|
if (name >= name_end) {
|
|
if (entry >= entry_end) {
|
|
return MAT_EQ;
|
|
} else {
|
|
return percent;
|
|
}
|
|
} else {
|
|
/* See if we can find a match with wildcards */
|
|
|
|
if (*name != '*') {
|
|
if (percent == MAT_NE)
|
|
return MAT_NE;
|
|
if (entry < entry_end)
|
|
if (toupper(*entry) > toupper(*name))
|
|
return MAT_GT;
|
|
return MAT_LT;
|
|
}
|
|
/* Strip out wildcard(s) - if end then we match! */
|
|
|
|
do {
|
|
name++;
|
|
} while (name < name_end && *name == '*');
|
|
if (name >= name_end)
|
|
return MAT_EQ;
|
|
|
|
/* Proceed to examine the specification past the wildcard... */
|
|
|
|
while (name < name_end) {
|
|
register int offset = 1;
|
|
register char fch = toupper(*name++);
|
|
|
|
/* See if can can find a match for the first character... */
|
|
|
|
if (fch != '%') {
|
|
while (entry < entry_end) {
|
|
if (toupper(*entry) != fch) {
|
|
entry++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* Give up if we can't find that one lousy character... */
|
|
|
|
if (entry >= entry_end)
|
|
return MAT_NE;
|
|
entry++;
|
|
|
|
/* See how much of the rest we can match... */
|
|
|
|
while (name < name_end && entry < entry_end) {
|
|
register char sch = *name, ech;
|
|
if (sch == '*')
|
|
break;
|
|
if (sch != (ech = *entry))
|
|
if (toupper(sch) != toupper(ech))
|
|
if (sch != '%')
|
|
break;
|
|
name++;
|
|
entry++;
|
|
offset++;
|
|
}
|
|
|
|
/* If matching died because of a wildcard we are OK... */
|
|
|
|
if (name < name_end && *name == '*') {
|
|
do {
|
|
name++;
|
|
} while (name < name_end && *name == '*');
|
|
if (name >= name_end)
|
|
return MAT_EQ;
|
|
|
|
/* Otherwise we finished OK or we need to try again... */
|
|
|
|
} else {
|
|
if (name >= name_end && entry >= entry_end)
|
|
return MAT_EQ;
|
|
name -= offset;
|
|
entry -= (size_t)offset - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* No more specification - match depends on remainder of entry... */
|
|
|
|
if (entry < entry_end)
|
|
return MAT_NE;
|
|
return MAT_EQ;
|
|
}
|
|
|
|
/*************************************************************** insert_ent() */
|
|
|
|
/* insert_ent() - procedure to add a directory entry at record dr entry de */
|
|
|
|
vmscond_t insert_ent( struct FCB * fcb, uint32_t eofblk, uint32_t curblk,
|
|
struct VIOC * vioc, char *buffer,
|
|
struct dir$r_rec * dr, struct dir$r_ent * de,
|
|
char *filename, uint32_t filelen,
|
|
uint32_t version, struct fiddef * fid,
|
|
uint16_t *reslen, struct dsc$descriptor *resdsc ) {
|
|
register vmscond_t sts = SS$_NORMAL;
|
|
register size_t inuse = 0;
|
|
register uint16_t addlen;
|
|
uint16_t verlimit;
|
|
size_t avail = 0, len;
|
|
char *p, vbuf[sizeof(";65535")];
|
|
|
|
verlimit = version >> 16;
|
|
version &= 0xffff;
|
|
|
|
/* Return result */
|
|
|
|
if( resdsc != NULL && resdsc->dsc$w_length != 0 ) {
|
|
avail = resdsc->dsc$w_length;
|
|
p = resdsc->dsc$a_pointer;
|
|
len = filelen;
|
|
if( len > avail )
|
|
len = avail;
|
|
memcpy( p, filename, len );
|
|
avail -= len;
|
|
p += len;
|
|
len = snprintf( vbuf, sizeof( vbuf ), ";%u", version );
|
|
if( len > avail )
|
|
len = avail;
|
|
memcpy( p, vbuf, len );
|
|
p += len;
|
|
*reslen = (unsigned short)(p - resdsc->dsc$a_pointer);
|
|
}
|
|
|
|
/* Compute space required... */
|
|
|
|
addlen = sizeof(struct dir$r_ent);
|
|
direct_inserts++;
|
|
if (de == NULL)
|
|
addlen += (((size_t)filelen + 1) & ~1) + offsetof(struct dir$r_rec, dir$t_name);
|
|
|
|
/* Compute block space in use ... */
|
|
|
|
{
|
|
int invalid_dir = TRUE;
|
|
while (TRUE) {
|
|
register size_t reclen;
|
|
register struct dir$r_rec *nr;
|
|
|
|
nr = (struct dir$r_rec *) (buffer + inuse);
|
|
|
|
if( dr == nr )
|
|
invalid_dir = FALSE;
|
|
reclen = F11WORD(nr->dir$w_size);
|
|
if (reclen == 0xffff) /* End of data marker */
|
|
break;
|
|
reclen += 2;
|
|
inuse += reclen;
|
|
reclen -= (((size_t)nr->dir$b_namecount + 1) & ~1) + offsetof(struct dir$r_rec, dir$t_name);
|
|
if (inuse > MAXREC || (inuse & 1) || reclen <= 0 ||
|
|
reclen % sizeof(struct dir$r_ent) != 0) {
|
|
deaccesschunk( vioc, 0, 0, FALSE );
|
|
return SS$_BADIRECTORY;
|
|
}
|
|
}
|
|
|
|
if (invalid_dir) {
|
|
printf("BUGCHECK invalid dir\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (de != NULL) {
|
|
if (F11WORD(dr->dir$w_size) > MAXREC ||
|
|
(char *) de < dr->dir$t_name + dr->dir$b_namecount ||
|
|
(char *) de > (char *) dr + F11WORD(dr->dir$w_size) + 2) {
|
|
printf("BUGCHECK invalid de\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If not enough space free extend the directory... */
|
|
|
|
if (addlen > MAXREC - inuse) {
|
|
register struct dir$r_rec *nr;
|
|
unsigned keep_new = 0;
|
|
char *newbuf;
|
|
struct VIOC *newvioc;
|
|
uint32_t newblk = eofblk + 1;
|
|
|
|
direct_splits++;
|
|
#if DEBUG
|
|
printf( "Splitting directory record... %s", dr->dir$t_name );
|
|
if( de != NULL )
|
|
printf( " %u,%u,%u,%u\n", de->dir$w_fid.fid$w_num,
|
|
de->dir$w_fid.fid$w_seq, de->dir$w_fid.fid$b_rvn, de->dir$w_fid.fid$b_nmx );
|
|
else
|
|
putchar( '\n' );
|
|
#endif
|
|
if( newblk > fcb->hiblock ) {
|
|
#if DEBUG
|
|
printf("Extending directory during split\n");
|
|
#endif
|
|
if( $FAILS(sts = update_extend( fcb, newblk - fcb->hiblock, 0 )) ) {
|
|
#if DEBUG
|
|
printf( "Failed to extend a directory during a split\n" );
|
|
#endif
|
|
return $SETLEVEL(SS$_DIRALLOC, SEVERE);
|
|
}
|
|
}
|
|
|
|
if( fcb->highwater != 0 && newblk > fcb->highwater -1 ) {
|
|
fcb->highwater = newblk + 1;
|
|
fcb->head->fh2$l_highwater = F11LONG( newblk + 1 );
|
|
}
|
|
|
|
if( $SUCCESSFUL(sts = accesschunk( fcb, newblk, &newvioc, &newbuf, NULL, 1 )) ) {
|
|
while( newblk > curblk + 1 ) {
|
|
char *frombuf;
|
|
struct VIOC *fromvioc;
|
|
|
|
if( $FAILED(sts = accesschunk( fcb, newblk - 1, &fromvioc, &frombuf, NULL, 1 )) )
|
|
break;
|
|
memcpy( newbuf, frombuf, BLOCKSIZE );
|
|
sts = deaccesschunk( newvioc, newblk, 1, TRUE);
|
|
newvioc = fromvioc;
|
|
newbuf = frombuf;
|
|
newblk--;
|
|
if( $FAILED(sts) )
|
|
break;
|
|
}
|
|
} else {
|
|
newvioc = NULL;
|
|
}
|
|
if( $FAILED(sts) ) {
|
|
if( newvioc != NULL )
|
|
deaccesschunk( newvioc, 0, 0, FALSE);
|
|
deaccesschunk( vioc, 0, 0, FALSE );
|
|
return sts;
|
|
}
|
|
memset( newbuf, 0, BLOCKSIZE );
|
|
eofblk++;
|
|
fcb->head->fh2$w_recattr.fat$l_efblk = F11SWAP( eofblk + 1 );
|
|
|
|
/* First find where the next record is... */
|
|
|
|
nr = dr;
|
|
if (F11WORD(nr->dir$w_size) <= MAXREC)
|
|
nr = (struct dir$r_rec *) ((char *) nr + F11WORD(nr->dir$w_size) + 2);
|
|
|
|
/* Can we split between records? */
|
|
|
|
if (de == NULL || (char *) dr != buffer ||
|
|
F11WORD(nr->dir$w_size) <= MAXREC) {
|
|
register struct dir$r_rec *sp;
|
|
|
|
sp = dr;
|
|
if ((char *) dr == buffer && de != NULL)
|
|
sp = nr;
|
|
memcpy(newbuf, sp, ((buffer + BLOCKSIZE) - (char *) sp));
|
|
memset(sp, 0, ((buffer + BLOCKSIZE) - (char *) sp));
|
|
sp->dir$w_size = F11WORD(0xffff);
|
|
if (sp == dr &&
|
|
(de != NULL || (char *) sp >= buffer + MAXREC - addlen)) {
|
|
if (de != NULL)
|
|
de = (struct dir$r_ent *)
|
|
(newbuf + ((char *) de - (char *) sp));
|
|
dr = (struct dir$r_rec *) (newbuf + ((char *) dr - (char *) sp));
|
|
keep_new = 1;
|
|
}
|
|
/* OK, we have to split the record then.. */
|
|
|
|
} else {
|
|
register uint32_t reclen;
|
|
register struct dir$r_rec *nbr;
|
|
|
|
reclen = ( (((size_t)dr->dir$b_namecount + 1) & ~1) +
|
|
offsetof(struct dir$r_rec, dir$t_name) );
|
|
nbr = (struct dir$r_rec *) newbuf;
|
|
#if DEBUG
|
|
printf("Super split %s %u,%u,%u,%u\n", dr->dir$t_name, de->dir$w_fid.fid$w_num,
|
|
de->dir$w_fid.fid$w_seq, de->dir$w_fid.fid$b_rvn, de->dir$w_fid.fid$b_nmx);
|
|
#endif
|
|
memcpy(newbuf, buffer, reclen);
|
|
memcpy(newbuf + reclen, de, ((char *) nr - (char *) de) + 2);
|
|
nbr->dir$w_size = F11WORD(reclen + ((char *) nr - (char *) de) - 2);
|
|
|
|
memset((char *) de + 2, 0, ((char *) nr - (char *) de));
|
|
((struct dir$r_rec *) de)->dir$w_size = F11WORD(0xffff);
|
|
dr->dir$w_size = F11WORD(((char *) de - (char *) dr) - 2);
|
|
if ((char *) de >= (char *) nr) {
|
|
dr = (struct dir$r_rec *) newbuf;
|
|
de = (struct dir$r_ent *) (newbuf + reclen);
|
|
keep_new = 1;
|
|
}
|
|
}
|
|
|
|
/* Need to decide which buffer we are going to keep (to write to) */
|
|
|
|
if (keep_new) {
|
|
sts = deaccesschunk( vioc, curblk, 1, TRUE);
|
|
curblk = newblk;
|
|
vioc = newvioc;
|
|
buffer = newbuf;
|
|
} else {
|
|
sts = deaccesschunk( newvioc, newblk, 1, TRUE );
|
|
}
|
|
if( $FAILED(sts) )
|
|
printf("I/O error splitting directory record: %s\n", getmsg(sts, 0));
|
|
}
|
|
/* After that we can just add the record or entry as appropriate... */
|
|
|
|
if (de == NULL) {
|
|
memmove((char *) dr + addlen, dr,
|
|
BLOCKSIZE - (((char *) dr + addlen) - buffer));
|
|
dr->dir$w_size = F11WORD(addlen - 2);
|
|
dr->dir$w_verlimit = verlimit;
|
|
dr->dir$b_flags = 0;
|
|
dr->dir$b_namecount = filelen;
|
|
memcpy(dr->dir$t_name, filename, filelen);
|
|
de = (struct dir$r_ent *)
|
|
((char *) dr + (addlen - sizeof(struct dir$r_ent)));
|
|
} else {
|
|
dr->dir$w_size = F11WORD(F11WORD(dr->dir$w_size) + addlen);
|
|
memmove((char *) de + addlen, de,
|
|
BLOCKSIZE - (((char *) de + addlen) - buffer));
|
|
}
|
|
|
|
/* Write the entry values are we are done! */
|
|
|
|
de->dir$w_version = F11WORD(version);
|
|
fid_copy( &de->dir$w_fid, fid, 0);
|
|
return deaccesschunk( vioc, curblk, 1, TRUE );
|
|
}
|
|
|
|
/*************************************************************** delete_ent() */
|
|
|
|
/* delete_ent() - delete a directory entry */
|
|
|
|
vmscond_t delete_ent( struct FCB * fcb, struct VIOC * vioc, uint32_t curblk,
|
|
struct dir$r_rec * dr, struct dir$r_ent * de,
|
|
char *buffer, uint32_t eofblk ) {
|
|
vmscond_t sts = SS$_NORMAL;
|
|
uint32_t ent;
|
|
|
|
direct_deletes++;
|
|
ent = ((size_t)F11WORD(dr->dir$w_size) + 2 - offsetof(struct dir$r_rec, dir$t_name)
|
|
- (((size_t)dr->dir$b_namecount + 1) & ~1)) / sizeof(struct dir$r_ent);
|
|
if (ent > 1) {
|
|
char *ne;
|
|
|
|
ne = (char *) de + sizeof(struct dir$r_ent);
|
|
memmove( de, ne, BLOCKSIZE - (ne - buffer) );
|
|
dr->dir$w_size = F11WORD(F11WORD(dr->dir$w_size) - sizeof(struct dir$r_ent));
|
|
} else {
|
|
char *nr;
|
|
|
|
nr = (char *) dr + F11WORD(dr->dir$w_size) + 2;
|
|
if( eofblk == 1 || (char *) dr > buffer ||
|
|
(nr <= buffer + MAXREC && (unsigned short) *nr < BLOCKSIZE) ) {
|
|
memmove(dr, nr, BLOCKSIZE - (nr - buffer));
|
|
} else {
|
|
while (curblk < eofblk) {
|
|
char *nxtbuffer;
|
|
struct VIOC *nxtvioc;
|
|
|
|
sts = accesschunk(fcb, curblk + 1, &nxtvioc, &nxtbuffer, NULL, 1);
|
|
if( $FAILED(sts) )
|
|
break;
|
|
memcpy(buffer, nxtbuffer, BLOCKSIZE);
|
|
if( $FAILS(sts = deaccesschunk(vioc, curblk++, 1, TRUE)) )
|
|
break;
|
|
buffer = nxtbuffer;
|
|
vioc = nxtvioc;
|
|
}
|
|
if( $SUCCESSFUL(sts) ) {
|
|
fcb->head->fh2$w_recattr.fat$l_efblk = F11SWAP(eofblk);
|
|
eofblk--;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
vmscond_t retsts;
|
|
|
|
retsts = deaccesschunk(vioc, curblk, 1, TRUE);
|
|
if( $SUCCESSFUL(sts) )
|
|
sts = retsts;
|
|
return sts;
|
|
}
|
|
}
|
|
|
|
/*************************************************************** return_ent() */
|
|
|
|
/* return_ent() - return information about a directory entry */
|
|
|
|
vmscond_t return_ent(struct FCB * fcb, struct VIOC * vioc, uint32_t curblk,
|
|
struct dir$r_rec * dr, struct dir$r_ent * de, struct fibdef * fib,
|
|
uint16_t *reslen, struct dsc_descriptor * resdsc,
|
|
int wildcard) {
|
|
if( resdsc != NULL ) {
|
|
register uint32_t scale = 10;
|
|
register uint16_t version;
|
|
register size_t length;
|
|
register char *ptr;
|
|
register size_t outlen;
|
|
|
|
version = F11WORD(de->dir$w_version);
|
|
length = dr->dir$b_namecount;
|
|
ptr = resdsc->dsc_a_pointer;
|
|
outlen = resdsc->dsc_w_length;
|
|
|
|
if (length > outlen)
|
|
length = outlen;
|
|
memcpy(ptr, dr->dir$t_name, length);
|
|
while( version >= scale )
|
|
scale *= 10;
|
|
ptr += length;
|
|
if( length < outlen ) {
|
|
*ptr++ = ';';
|
|
length++;
|
|
do {
|
|
if( length >= outlen )
|
|
break;
|
|
scale /= 10;
|
|
*ptr++ = version / scale + '0';
|
|
version %= scale;
|
|
length++;
|
|
} while( scale > 1 );
|
|
}
|
|
*reslen = (uint16_t)length;
|
|
}
|
|
fid_copy( (struct fiddef *)&fib->fib$w_fid_num, &de->dir$w_fid, 0 );
|
|
|
|
if( fib->fib$b_fid_rvn == 0 )
|
|
fib->fib$b_fid_rvn = fcb->rvn;
|
|
|
|
fib->fib$w_verlimit = dr->dir$w_verlimit;
|
|
|
|
if( wildcard || (fib->fib$w_nmctl & FIB$M_WILD) ) {
|
|
fib->fib$l_wcc = curblk;
|
|
} else {
|
|
fib->fib$l_wcc = 0;
|
|
}
|
|
return deaccesschunk(vioc, 0, 0, TRUE);
|
|
}
|
|
|
|
/*************************************************************** search_ent() */
|
|
|
|
/* search_ent() - search for a directory entry */
|
|
|
|
vmscond_t search_ent( struct FCB *fcb,
|
|
struct dsc_descriptor *fibdsc,
|
|
struct dsc_descriptor *filedsc, uint16_t *reslen,
|
|
struct dsc_descriptor *resdsc, uint32_t eofblk,
|
|
unsigned action ) {
|
|
register vmscond_t sts;
|
|
uint32_t curblk;
|
|
struct VIOC *vioc = NULL;
|
|
char *searchspec, *buffer;
|
|
int searchlen, version, wildcard, wcc_flag;
|
|
struct fibdef *fib;
|
|
|
|
fib = (struct fibdef *) fibdsc->dsc_a_pointer;
|
|
direct_lookups++;
|
|
|
|
/* 1) Generate start block (wcc gives start point)
|
|
* 2) Search for start
|
|
* 3) Scan until found or too big or end.
|
|
*/
|
|
|
|
curblk = fib->fib$l_wcc;
|
|
if (curblk != 0) {
|
|
searchspec = resdsc->dsc_a_pointer;
|
|
sts = name_check( searchspec, *reslen, &searchlen, &version, &wildcard );
|
|
if( MODIFIES(action) || wildcard )
|
|
sts = RMS$_WLD;
|
|
wcc_flag = TRUE;
|
|
} else {
|
|
searchspec = filedsc->dsc_a_pointer;
|
|
sts = name_check( searchspec, filedsc->dsc_w_length, &searchlen, &version,
|
|
&wildcard);
|
|
if( (MODIFIES(action) && wildcard) || (action == DIRECT_CREATE && version < 0) ) {
|
|
sts = RMS$_WLD;
|
|
}
|
|
wcc_flag = FALSE;
|
|
}
|
|
if( $FAILED(sts) )
|
|
return sts;
|
|
|
|
/* Identify starting block...*/
|
|
|
|
if (*searchspec == '*' || *searchspec == '%') {
|
|
curblk = 1;
|
|
} else {
|
|
register uint32_t loblk = 1, hiblk = eofblk;
|
|
if( curblk < 1 || curblk > eofblk )
|
|
curblk = (eofblk + 1) / 2;
|
|
|
|
while (loblk < hiblk) {
|
|
register int cmp;
|
|
register uint32_t newblk;
|
|
register struct dir$r_rec *dr;
|
|
direct_searches++;
|
|
|
|
if( $FAILS(sts = accesschunk( fcb, curblk, &vioc, &buffer, NULL, MODIFIES(action) )) )
|
|
return sts;
|
|
dr = (struct dir$r_rec *) buffer;
|
|
if( F11WORD(dr->dir$w_size) > MAXREC ) {
|
|
cmp = MAT_GT;
|
|
} else {
|
|
cmp = name_match( searchspec, searchlen, dr->dir$t_name,
|
|
dr->dir$b_namecount );
|
|
if (cmp == MAT_EQ) {
|
|
if (wildcard || version < 1 || version > 32767) {
|
|
cmp = MAT_NE; /* no match - want to find start */
|
|
} else {
|
|
register struct dir$r_ent *de =
|
|
(struct dir$r_ent *)
|
|
(dr->dir$t_name + ((dr->dir$b_namecount + 1) & ~1));
|
|
if (F11WORD(de->dir$w_version) < version) {
|
|
cmp = MAT_GT; /* too far... */
|
|
} else {
|
|
if (F11WORD(de->dir$w_version) > version) {
|
|
cmp = MAT_LT; /* further ahead... */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
switch (cmp) {
|
|
case MAT_LT:
|
|
if (curblk == fib->fib$l_wcc) {
|
|
newblk = hiblk = loblk = curblk;
|
|
} else {
|
|
loblk = curblk;
|
|
newblk = (loblk + hiblk + 1) / 2;
|
|
}
|
|
break;
|
|
case MAT_GT:
|
|
case MAT_NE:
|
|
newblk = (loblk + curblk) / 2;
|
|
hiblk = curblk - 1;
|
|
break;
|
|
default:
|
|
newblk = hiblk = loblk = curblk;
|
|
}
|
|
if (newblk != curblk) {
|
|
if( $FAILS(sts = deaccesschunk( vioc, 0, 0, TRUE )) )
|
|
return sts;
|
|
vioc = NULL;
|
|
curblk = newblk;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now to read sequentially to find entry... */
|
|
|
|
{
|
|
char last_name[80];
|
|
unsigned last_len = 0;
|
|
register int relver = 0;
|
|
while( $SUCCESSFUL(sts) && curblk <= eofblk ) {
|
|
register struct dir$r_rec *dr;
|
|
register int cmp = MAT_LT;
|
|
|
|
/* Access a directory block. Reset relative version if it starts
|
|
with a record we haven't seen before... */
|
|
|
|
if( vioc == NULL ) {
|
|
if( $FAILS(sts = accesschunk( fcb, curblk, &vioc,
|
|
&buffer, NULL, MODIFIES(action) )) )
|
|
return sts;
|
|
}
|
|
dr = (struct dir$r_rec *) buffer;
|
|
if (last_len != dr->dir$b_namecount) {
|
|
relver = 0;
|
|
} else {
|
|
if( name_match(last_name, last_len, dr->dir$t_name, last_len) !=
|
|
MAT_EQ ) {
|
|
relver = 0;
|
|
}
|
|
}
|
|
|
|
/* Now loop through the records seeing which match our spec... */
|
|
|
|
while (TRUE) { /* dr records within block */
|
|
register char *nr = (char *) dr + F11WORD(dr->dir$w_size) + 2;
|
|
if (nr >= buffer + BLOCKSIZE)
|
|
break;
|
|
if (dr->dir$t_name + dr->dir$b_namecount >= nr)
|
|
break;
|
|
cmp = name_match(searchspec, searchlen, dr->dir$t_name,
|
|
dr->dir$b_namecount);
|
|
if (cmp == MAT_GT && wcc_flag) {
|
|
wcc_flag = FALSE;
|
|
searchspec = filedsc->dsc_a_pointer;
|
|
if( $FAILS(sts = name_check(searchspec, filedsc->dsc_w_length,
|
|
&searchlen, &version, &wildcard)) )
|
|
break;
|
|
} else {
|
|
if (cmp == MAT_EQ) {
|
|
register struct dir$r_ent *de;
|
|
de = (struct dir$r_ent *)
|
|
(dr->dir$t_name +
|
|
((dr->dir$b_namecount + 1) & ~1));
|
|
if (version == 0 && action == DIRECT_CREATE) {
|
|
version = F11WORD(de->dir$w_version) + 1;
|
|
if (version > 32767) {
|
|
sts = RMS$_VER;
|
|
break;
|
|
}
|
|
}
|
|
/* Look at each directory entry to see
|
|
if it is what we want... */
|
|
|
|
if ((char *) dr != buffer) relver = 0;
|
|
cmp = MAT_LT;
|
|
while ((char *) de < nr) {
|
|
if ((version < 1) ?
|
|
(relver > version) :
|
|
(version < F11WORD(de->dir$w_version))) {
|
|
relver--;
|
|
de++;
|
|
} else {
|
|
if (version > 32767 || version == relver ||
|
|
version == F11WORD(de->dir$w_version)) {
|
|
cmp = MAT_EQ;
|
|
} else {
|
|
cmp = MAT_GT;
|
|
}
|
|
if (!wcc_flag) {
|
|
break;
|
|
} else {
|
|
wcc_flag = FALSE;
|
|
searchspec = filedsc->dsc_a_pointer;
|
|
if( $FAILS(sts = name_check( searchspec,
|
|
filedsc->dsc_w_length,
|
|
&searchlen, &version,
|
|
&wildcard )) )
|
|
break;
|
|
if (name_match(searchspec, searchlen,
|
|
dr->dir$t_name,
|
|
dr->dir$b_namecount) !=
|
|
MAT_EQ) {
|
|
cmp = MAT_NE;
|
|
break;
|
|
}
|
|
if (version < 0) {
|
|
relver = -32768;
|
|
cmp = MAT_GT;
|
|
break;
|
|
}
|
|
if (cmp == MAT_EQ) {
|
|
relver--;
|
|
de++;
|
|
}
|
|
cmp = MAT_LT;
|
|
}
|
|
}
|
|
}
|
|
if( $FAILED(sts) )
|
|
break;
|
|
|
|
/* Decide what to do with the entry we have found... */
|
|
|
|
if (cmp == MAT_EQ) {
|
|
switch (action) {
|
|
case DIRECT_FIND:
|
|
return return_ent( fcb, vioc, curblk, dr, de, fib,
|
|
reslen, resdsc, wildcard );
|
|
case DIRECT_DELETE:
|
|
return delete_ent( fcb, vioc, curblk, dr, de,
|
|
buffer, eofblk );
|
|
case DIRECT_CREATE:
|
|
sts = SS$_DUPFILENAME;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
} else {
|
|
if( cmp != MAT_NE && action == DIRECT_CREATE ) {
|
|
return insert_ent( fcb, eofblk, curblk, vioc, buffer,
|
|
dr, de, searchspec, searchlen,
|
|
(fib->fib$w_verlimit << 16) | version,
|
|
(struct fiddef *)&fib->fib$w_fid_num, reslen, resdsc );
|
|
|
|
}
|
|
}
|
|
}
|
|
/* Finish unless we expect more... */
|
|
|
|
if (cmp == MAT_GT && !wildcard)
|
|
break;
|
|
|
|
/* If this is the last record in the block store the name
|
|
so that if it continues into the next block we can get
|
|
the relative version right! Sigh! */
|
|
|
|
if (F11WORD(((struct dir$r_rec *) nr)->dir$w_size) > MAXREC) {
|
|
last_len = dr->dir$b_namecount;
|
|
if (last_len > sizeof(last_name)) {
|
|
last_len = sizeof(last_name);
|
|
}
|
|
memcpy(last_name, dr->dir$t_name, last_len);
|
|
dr = (struct dir$r_rec *) nr;
|
|
break;
|
|
}
|
|
dr = (struct dir$r_rec *) nr;
|
|
}
|
|
}
|
|
|
|
/* Release the buffer ready to get the next one - unless it is the
|
|
last one in which case we can't defer an insert any longer!! */
|
|
|
|
if( $FAILED(sts) || action != DIRECT_CREATE ||
|
|
(cmp != MAT_GT && curblk < eofblk)) {
|
|
register vmscond_t dests;
|
|
|
|
if( $FAILS(dests = deaccesschunk( vioc, 0, 0, TRUE )) ) {
|
|
sts = dests;
|
|
break;
|
|
}
|
|
vioc = NULL;
|
|
curblk++;
|
|
} else {
|
|
if( version == 0 )
|
|
version = 1;
|
|
return insert_ent( fcb, eofblk, curblk, vioc, buffer, dr, NULL,
|
|
searchspec, searchlen, (fib->fib$w_verlimit << 16) | version,
|
|
(struct fiddef *)&fib->fib$w_fid_num,
|
|
reslen, resdsc );
|
|
}
|
|
} /* curblk blocks within file */
|
|
}
|
|
|
|
/* We achieved nothing! Report the failure... */
|
|
|
|
if( $SUCCESSFUL(sts) ) {
|
|
fib->fib$l_wcc = 0;
|
|
if (wcc_flag || wildcard) {
|
|
sts = SS$_NOMOREFILES;
|
|
} else {
|
|
sts = SS$_NOSUCHFILE;
|
|
}
|
|
}
|
|
return sts;
|
|
}
|
|
|
|
/******************************************************************* direct() */
|
|
|
|
/* direct() - this routine handles all directory manipulations:-
|
|
action 0 - find directory entry
|
|
1 - delete entry
|
|
2 - create an entry
|
|
*/
|
|
|
|
unsigned direct( struct VCB * vcb, struct dsc_descriptor * fibdsc,
|
|
struct dsc_descriptor * filedsc, uint16_t *reslen,
|
|
struct dsc_descriptor * resdsc, unsigned action ) {
|
|
struct FCB *fcb;
|
|
register vmscond_t sts;
|
|
uint32_t eofblk;
|
|
register struct fibdef *fib;
|
|
|
|
fib = (struct fibdef *) fibdsc->dsc_a_pointer;
|
|
|
|
if( $SUCCESSFUL(sts = accessfile( vcb, (struct fiddef *) &fib->fib$w_did_num,
|
|
&fcb, MODIFIES(action) )) ) {
|
|
if (F11LONG(fcb->head->fh2$l_filechar) & FH2$M_DIRECTORY) {
|
|
eofblk = F11SWAP(fcb->head->fh2$w_recattr.fat$l_efblk);
|
|
if( F11WORD(fcb->head->fh2$w_recattr.fat$w_ffbyte) == 0 )
|
|
--eofblk;
|
|
if( eofblk == 0 ) {
|
|
struct VIOC *vioc;
|
|
char *buffer;
|
|
uint32_t blocks;
|
|
|
|
if( action != DIRECT_CREATE ) {
|
|
int searchlen, version, wildcard;
|
|
|
|
deaccessfile( fcb );
|
|
if( fib->fib$l_wcc ) {
|
|
if( $FAILS(sts = name_check( resdsc->dsc$a_pointer, *reslen,
|
|
&searchlen, &version, &wildcard )) )
|
|
return sts;
|
|
fib->fib$l_wcc = 0;
|
|
return SS$_NOMOREFILES;
|
|
}
|
|
if( $FAILS(sts = name_check( filedsc->dsc$a_pointer,
|
|
filedsc->dsc$w_length,
|
|
&searchlen, &version, &wildcard )) )
|
|
return sts;
|
|
if( wildcard )
|
|
return SS$_NOMOREFILES;
|
|
return SS$_NOSUCHFILE;
|
|
}
|
|
|
|
eofblk = F11SWAP(fcb->head->fh2$w_recattr.fat$l_hiblk);
|
|
if( eofblk == 0 ) {
|
|
if( $FAILS(sts = update_extend( fcb, 1, 0 )) ) {
|
|
deaccessfile( fcb );
|
|
return sts;
|
|
}
|
|
eofblk = F11SWAP(fcb->head->fh2$w_recattr.fat$l_hiblk);
|
|
if( fcb->highwater != 0 ) {
|
|
fcb->highwater = 2;
|
|
fcb->head->fh2$l_highwater = F11SWAP( 2 );
|
|
}
|
|
}
|
|
fcb->head->fh2$w_recattr.fat$l_efblk = F11SWAP( 2 );
|
|
fcb->head->fh2$w_recattr.fat$w_ffbyte = 0;
|
|
eofblk = 1;
|
|
if( $FAILS(sts = accesschunk( fcb, 1, &vioc, &buffer, &blocks, 1 )) ) {
|
|
deaccessfile( fcb );
|
|
return sts;
|
|
}
|
|
memset( buffer, 0, 512 );
|
|
((f11word *)buffer)[0] = 0xffff;
|
|
deaccesschunk( vioc, 1, blocks, TRUE );
|
|
}
|
|
sts = search_ent( fcb, fibdsc, filedsc, reslen, resdsc, eofblk, action );
|
|
} else {
|
|
sts = SS$_BADIRECTORY;
|
|
}
|
|
{
|
|
register uint32_t dests;
|
|
|
|
dests = deaccessfile( fcb );
|
|
if( $SUCCESSFUL(sts) )
|
|
sts = dests;
|
|
}
|
|
}
|
|
if( MODIFIES(action) ) {
|
|
cache_flush();
|
|
while( vcb->dircache ) /* Too conservative...but safe */
|
|
cache_delete( (struct CACHE *)vcb->dircache );
|
|
}
|
|
|
|
return sts;
|
|
}
|
|
|
|
/******************************************************************* dircmp() */
|
|
|
|
/* Function to compare directory cache names */
|
|
|
|
static int dircmp( uint32_t keylen, void *key, void *node ) {
|
|
register struct DIRCACHE *dirnode;
|
|
register int cmp;
|
|
unsigned dlen;
|
|
const char *kp, *dp;
|
|
int version, wild, namelen;
|
|
vmscond_t sts;
|
|
|
|
if( $FAILS(sts = name_check(key, keylen, &namelen, &version, &wild) ) ) {
|
|
abort();
|
|
}
|
|
|
|
dirnode = (struct DIRCACHE *) node;
|
|
|
|
if( version != F11WORD(dirnode->entry.dir$w_version) )
|
|
return version - F11WORD(dirnode->entry.dir$w_version);
|
|
|
|
dlen = dirnode->record.dir$b_namecount;
|
|
|
|
kp = (const char *)key;
|
|
dp = dirnode->record.dir$t_name;
|
|
|
|
while( keylen > 0 && dlen > 0 ) {
|
|
cmp = toupper(*kp++) - toupper(*dp++);
|
|
if( cmp != 0 )
|
|
return cmp;
|
|
--keylen;
|
|
--dlen;
|
|
}
|
|
return keylen - dlen;
|
|
}
|
|
|
|
/***************************************************************** dir_create() */
|
|
static void *dir_create( uint32_t keylen, void *key, vmscond_t *retsts ) {
|
|
struct DIRCACHE *dir;
|
|
vmscond_t sts;
|
|
int version, wild, namelen;
|
|
|
|
if( $FAILS(sts = name_check(key, keylen, &namelen, &version, &wild) ) ) {
|
|
*retsts = RMS$_DIR;
|
|
return NULL;
|
|
}
|
|
if( wild || version == 0 ) {
|
|
*retsts = SS$_NOSUCHFILE;
|
|
return NULL;
|
|
}
|
|
|
|
if( (dir = (struct DIRCACHE *)calloc( 1,
|
|
offsetof( struct DIRCACHE, record.dir$t_name ) +
|
|
namelen + (namelen & 1) )) == NULL ) {
|
|
*retsts = SS$_INSFMEM;
|
|
return NULL;
|
|
}
|
|
|
|
dir->cache.objtype = OBJTYPE_DIR;
|
|
dir->record.dir$w_size = (f11word)F11WORD( offsetof( struct dir$r_rec,
|
|
dir$t_name ) + namelen - 2);
|
|
dir->record.dir$b_namecount = (f11byte) namelen;
|
|
memcpy( dir->record.dir$t_name, key, namelen );
|
|
dir->entry.dir$w_version = (f11word)F11WORD((f11word)version);
|
|
|
|
*retsts = SS$_CREATED;
|
|
return dir;
|
|
}
|
|
|
|
/***************************************************************** direct_dirid() */
|
|
|
|
/* Find FID of directory/ies using a cache.
|
|
* Used by sys$parse. Can be generalized...
|
|
* Input descriptor is a directory name string, including subdirectories.
|
|
* Output is the fid of the lowest level directory.
|
|
* Higher directories are searched (and cached).
|
|
* Any wildcards/recursion stop the search (they'll be resolved when a
|
|
* search matches them.)
|
|
*/
|
|
|
|
vmscond_t direct_dirid( struct VCB *vcb, struct dsc$descriptor *dirdsc,
|
|
struct fiddef *dirid, struct fiddef *fid ) {
|
|
register struct DIRCACHE *dir = NULL;
|
|
vmscond_t sts;
|
|
struct fibdef fib;
|
|
struct dsc$descriptor fibdsc;
|
|
struct dsc$descriptor namdsc;
|
|
struct fiddef srcdir;
|
|
char *dirnam;
|
|
int dirlen;
|
|
size_t len;
|
|
char *bp, *dp;
|
|
char nambuf[256];
|
|
|
|
memset( &fib, 0, sizeof( fib ) );
|
|
memset( &fibdsc, 0, sizeof( fibdsc ) );
|
|
memset( &namdsc, 0, sizeof( namdsc ) );
|
|
memset( &srcdir, 0, sizeof( srcdir ) );
|
|
|
|
dirlen = dirdsc->dsc$w_length;
|
|
|
|
if( (size_t)dirlen > sizeof( nambuf ) -1 )
|
|
abort();
|
|
dirnam = dirdsc->dsc$a_pointer;
|
|
|
|
srcdir.fid$w_num = FID$C_MFD;
|
|
srcdir.fid$w_seq = FID$C_MFD;
|
|
srcdir.fid$b_rvn = 0;
|
|
srcdir.fid$b_nmx = 0;
|
|
|
|
if( dirlen < 1 || (dirlen == 6 && !memcmp( dirnam, "000000", 6 )) ) {
|
|
if( dirid )
|
|
*dirid = srcdir;
|
|
if( fid )
|
|
*fid = srcdir;
|
|
return SS$_NORMAL;
|
|
}
|
|
|
|
if( dirid != NULL )
|
|
memset( dirid, 0, sizeof( *dirid ) );
|
|
if( fid != NULL )
|
|
memset( fid, 0, sizeof( *fid ) );
|
|
|
|
fibdsc.dsc$w_length = sizeof( fibdsc );
|
|
fibdsc.dsc$a_pointer = (char *)&fib;
|
|
|
|
for( bp = dp = dirnam; dp <= dirnam + dirlen; dp++) {
|
|
char ch;
|
|
|
|
if( dp < dirnam + dirlen ) {
|
|
ch = *dp;
|
|
if( ch == '*' || ch == '%' ) {
|
|
return RMS$_WLD;
|
|
}
|
|
if( ch != '.' )
|
|
continue;
|
|
}
|
|
len = (size_t)(dp - bp);
|
|
if( len == 0 )
|
|
break;
|
|
|
|
memcpy( nambuf, bp, len );
|
|
namdsc.dsc$w_length = (uint16_t)(len + sizeof( ".DIR;1" ) -1);
|
|
memcpy( nambuf+len, ".DIR;1", namdsc.dsc$w_length );
|
|
|
|
namdsc.dsc$a_pointer = nambuf;
|
|
|
|
dir = cache_find( (void *) &vcb->dircache, namdsc.dsc$w_length, nambuf,
|
|
&sts, dircmp, dir_create );
|
|
if( dir == NULL ) {
|
|
return RMS$_DNF;
|
|
}
|
|
cache_untouch( &dir->cache, FALSE );
|
|
|
|
if( $MATCHCOND( sts, SS$_CREATED ) ) {
|
|
memset( &fib, 0, sizeof( fib ) );
|
|
memcpy( &fib.fib$w_did_num, &srcdir, sizeof( struct fiddef ) );
|
|
|
|
sts = direct( vcb, &fibdsc, &namdsc, NULL, NULL, DIRECT_FIND );
|
|
if( $FAILED(sts) ) {
|
|
cache_delete( &dir->cache );
|
|
return RMS$_DNF;
|
|
}
|
|
dir->parent = srcdir;
|
|
dir->record.dir$w_verlimit = fib.fib$w_verlimit;
|
|
memcpy( &dir->entry.dir$w_fid, &fib.fib$w_fid_num, sizeof( struct fiddef ) );
|
|
}
|
|
if( (dp + 2 < dirnam + dirlen) && dp[1] == '.' && dp[2] == '.' ) {
|
|
return RMS$_DNF;
|
|
}
|
|
srcdir = dir->entry.dir$w_fid;
|
|
bp = dp +1;
|
|
}
|
|
if( dir != NULL ) {
|
|
if( dirid )
|
|
*dirid = dir->parent;
|
|
if( fid )
|
|
*fid = dir->entry.dir$w_fid;
|
|
return SS$_NORMAL;
|
|
}
|
|
return RMS$_DNF;
|
|
}
|