home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
opendc12.zip
/
od124os2.exe
/
od12osr1.exe
/
src
/
CMRefOps.c
< prev
next >
Wrap
C/C++ Source or Header
|
1997-03-21
|
57KB
|
1,244 lines
/* @(#)Z 1.7 com/src/cm/CMRefOps.c, odstorage, od96os2, odos29712d 97/03/21 17:19:32 (96/10/29 09:16:58) */
/*====START_GENERATED_PROLOG======================================
*/
/*
* COMPONENT_NAME: odstorage
*
* CLASSES: none
*
* ORIGINS: 82,27
*
*
* (C) COPYRIGHT International Business Machines Corp. 1995,1996
* All Rights Reserved
* Licensed Materials - Property of IBM
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*
* IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
* USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
* OR PERFORMANCE OF THIS SOFTWARE.
*/
/*====END_GENERATED_PROLOG========================================
*/
/*
File: CMRefOps.c
Contains: Container Manager Object Reference Operations
Owned by: Ira L. Ruben
Owned by: Ed Lai
Copyright: ⌐ 1992-1995 by Apple Computer, Inc., all rights reserved.
Change History (most recent first):
<6> 6/29/95 DM #1263765 change sizeof(orgSize) to orgSize
for buffer allocation in CMSetReference
<5> 5/11/95 DM 1214394, 1245829: stop compiler warnings
<4> 2/17/95 EL 1182275: Forgot to move in CMKeepObject in
last checkin.
<3> 2/15/95 EL 1182275: Move CMKeepObject to CMRefOps and
CMKeepObject would now ignore referenced
objects that cannot be found.
<2> 8/26/94 EL #1182308 Updating reference object should
use the endian-ness of the reference object
and not the endian-ness of the updating
container.
<3> 5/10/94 EL #1162327. Need to delete the value rather
than just delete the data.
<2> 5/10/94 EL #1162327. When a value is moved from one
container to another and there is value in
, the data is now copied over.
<1> 2/3/94 EL first checked in
<6> 1/6/94 EL Do not free a value if it belongs to two
containers.
<5> 12/13/93 EL Temp fix for case where the RefDataObject
is deleleted when its length reach 0, but
during playback of update the value header
would not know the RefDataObject is now
gone.
<4> 12/7/93 EL Add CMGetReferenceForObject call.
<3> 10/6/93 EL Rename CMGetAdjacentXXXX to GetAdjacentXXXX
and static near.
<2> 10/4/93 EL Fix bug in CMDeleteReference, Fix bug of
reference of old value in updating
container.
To Do:
Fix CMGetNextReference so that it really looks at the reference to get the next one.
In progress:
*/
/*---------------------------------------------------------------------------*
| |
| <<< CMRefOps.c >>> |
| |
| Container Manager Object Reference Operations |
| |
| Ira L. Ruben |
| 10/15/92 |
| |
| Copyright Apple Computer, Inc. 1992-1994 |
| All rights reserved. |
| |
*---------------------------------------------------------------------------*
An object's value may contain data that refers to other objects. This file contains
all the API routines that allow a user to embed object ID references in value data. The
routines defined here allow that data to be manipulated without the API user explicitly
knowing the form such object references take.
But, just for the record, we better describe how the references are maintained here!
When a reference is to be saved in some value, the user calls CMNewReference() or
CMSetReference(). A reference key is to be associated with some object. The user
retrieves objects via their keys. We record the key/object (ID) associations. They
are recorded in a private recording object which is "tied" to the user's value from a
field (refDataObject) in the value header.
The recording object has one special property and one specially typed value for that
property. The value data for that value is the list of key/object ID associations. They
are formatted as 8-byte entries; 4 bytes for the key (which comes first), and 4 bytes for
the corresponding object ID.
This data is a linear list on the assumption there won't be "too many" references in each
value. We could be wrong about this.
The list is maintained as data because it is subject to updating like any other data!
The price we pay for this, however, is to do handler I/O to read and write this data.
To gain some efficiency, buffered I/O, using the routines in BufferIO.c , is used.
*/
#include <stddef.h>
#include <string.h>
#include <setjmp.h>
#include <stdio.h>
#ifndef __CMTYPES__
#include "CMTypes.h"
#endif
#ifndef __CM_API__
#include "CMAPI.h"
#endif
#ifndef __TOCENTRIES__
#include "TOCEnts.h"
#endif
#ifndef __TOCOBJECTS__
#include "TOCObjs.h"
#endif
#ifndef __GLOBALNAMES__
#include "GlbNames.h"
#endif
#ifndef __CONTAINEROPS__
#include "Containr.h"
#endif
#ifndef __HANDLERS__
#include "Handlers.h"
#endif
#ifndef __BUFFEREDIO__
#include "BufferIO.h"
#endif
#ifndef __LISTMGR__
#include "ListMgr.h"
#endif
#ifndef __REFERENCES__
#include "Refs.h"
#endif
#ifndef __SESSIONDATA__
#include "Session.h"
#endif
#ifndef __ERRORRPT__
#include "ErrorRpt.h"
#endif
#ifndef __UTILITYROUTINES__
#include "Utility.h"
#endif
CM_CFUNCTIONS
/* The following generates a segment directive for Mac only due to 32K Jump Table */
/* Limitations. If you don't know what I'm talking about don't worry about it. The */
/* default is not to generate the pragma. Theoritically unknown pragmas in ANSI won't */
/* choke compilers that don't recognize them. Besides why would they be looked at if */
/* it's being conditionally skipped over anyway? I know, famous last words! */
#if CM_MPW
#pragma segment CMReferenceOps
#endif
/* The references are always searched for based on the reference key in the data. One */
/* common routine, getReference(), is used to do this searhing. It returns the */
/* following status codes to indicate the results of its search. */
enum RefSearchStatus { /* getReference() search status: */
RefReadError, /* some read error occurred */
RefFound, /* returned offset == position & ID */
RefNotFound /* offset == list size, ID undefined */
};
typedef enum RefSearchStatus RefSearchStatus;
#if CMSHADOW_LIST
/*----------------------------------------------------------------------------------------*
| appendShadowListEntry - create/append in a single shadow list entry to its shadow list |
*----------------------------------------------------------------------------------------*
This internal routine creates and then appends a single shadow list entry to the specified
shadow list. The list header pointer, key, and its associated object ID are passed. The
container is used for allocating the entry and error reporting.
The function returns a pointer to the created entry. NULL is returned if an error is
reported for an allocation failure and the error reporter returns.
*/
static RefDataShadowEntryPtr CM_NEAR CM_PASCAL appendShadowListEntry(ContainerPtr container,
ListHdrPtr refShadowList,
CM_ULONG key,
CMObjectID objectID)
{
RefDataShadowEntryPtr refShadowEntry;
refShadowEntry = (RefDataShadowEntryPtr)CMmalloc(sizeof(RefDataShadowEntry));
if (refShadowEntry == NULL) {
ERROR1(CM_err_NoRefShadowList, CONTAINERNAME);
return (NULL);
}
cmNullListLinks(refShadowEntry);
refShadowEntry->key = key;
refShadowEntry->objectID = objectID;
return ((RefDataShadowEntryPtr)cmAppendListCell(refShadowList, refShadowEntry));
}
/*----------------------------------------------------------------------------*
| deleteShadowListEntry - delete a single recording object shadow list entry |
*----------------------------------------------------------------------------*
This internal routine is used to shadow CMDeleteReference(), i.e., to delete the
corresponding shadow list entry. The recording object's value header and the pointer to
the entry to be deleted are passed.
The pointer is the value returned by getReference() when CMDeleteReference() called it to
find the reference entry to be deleted.
If the last list entry is deleted, the list header itself is deleted and the pointer in
the value header set to NULL to indicate there is no shadow list.
*/
static void CM_NEAR CM_PASCAL deleteShadowListEntry(TOCValueHdrPtr refDataValueHdr,
RefDataShadowEntryPtr refShadowEntry)
{
ContainerPtr container = refDataValueHdr->container->targetContainer;
ListHdrPtr refShadowList = RefShadowList(refDataValueHdr);
if (refShadowList != NULL && refShadowEntry != NULL) {
CMfree(cmDeleteListCell(refShadowList, refShadowEntry)); /* delete the entry */
if (cmIsEmptyList(refShadowList)) { /* if no more entries... */
CMfree(refShadowList); /* ...delete the list hdr */
RefShadowList(refDataValueHdr) = NULL; /* ...indicate no list */
}
}
}
/*-----------------------------------------------------------------------------*
| cmDeleteRefDataShadowList - delete an entire recording object's shadow list |
*-----------------------------------------------------------------------------*
This routine is called to delete the entire shadow list pointed to from the specified
recording object's value header. It is used for clearing the list during error recovery
and value (header) deletions.
Note, generally the caller should have done a HasRefShadowList(refDataValueHdr) prior to
calling this routine to make sure that the value header is indeed a recording object
value header.
*/
void cmDeleteRefDataShadowList(TOCValueHdrPtr refDataValueHdr)
{
ContainerPtr container = refDataValueHdr->container->targetContainer;
ListHdrPtr refShadowList = RefShadowList(refDataValueHdr);
RefDataShadowEntryPtr refShadowEntry, nextEntry;
if (refShadowList != NULL) {
refShadowEntry = (RefDataShadowEntryPtr)cmGetListHead(refShadowList);
while (refShadowEntry) { /* delete all the list entries... */
nextEntry = (RefDataShadowEntryPtr)cmGetNextListCell(refShadowEntry);
CMfree(refShadowEntry);
refShadowEntry = nextEntry;
}
CMfree(refShadowList); /* delete the list header */
RefShadowList(refDataValueHdr) = NULL; /* indicate there's no shadow list */
}
}
#endif
/*---------------------------------------------------------------------------*
| getRecordingValueHdr - get recording object's reference list value header |
*---------------------------------------------------------------------------*
This is an internal routine used to return the value (header) pointer (refNum) for the
recording object's object/reference association list. NULL is returned if the recording
object doesn't exist or an error is reported and the error reporter returns.
The only possible errors here are that the property or the property's value header do not
exist in the recording object. This shoould never happen. But we check it anyway.
*/
static TOCValueHdrPtr CM_NEAR CM_PASCAL getRecordingValueHdr(TOCValueHdrPtr theValueHdr)
{
ContainerPtr container;
TOCPropertyPtr refDataProperty;
TOCValueHdrPtr refDataValueHdr;
if (!HasRefDataObject(theValueHdr)) /* if no recording object... */
return (NULL); /* ...not found */
/* Validate that we have the "proper" property and type in the recording object... */
/* Call me paranoid (ok, you're paranoid!) */
refDataProperty = cmGetObjectProperty(RefDataObject(theValueHdr), CM_StdObjID_ObjReferences);
if (refDataProperty) {
refDataValueHdr = (TOCValueHdrPtr)cmGetListHead(&refDataProperty->valueHdrList);
if (refDataValueHdr && refDataValueHdr->typeID != CM_StdObjID_ObjRefData)
refDataValueHdr = NULL;
}
if (refDataProperty == NULL || refDataValueHdr == NULL){/* gee, what went wrong? */
container = theValueHdr->container->targetContainer; /* this is an internal error! */
ERROR1(CM_err_Internal6, CONTAINERNAME);
return (NULL);
}
return (refDataValueHdr); /* return recording value hdr */
}
/*----------------------------------------------------------------------*
| getReference - find a reference recorded in a value's reference list |
*----------------------------------------------------------------------*
This is an internal routine used to search a recording object's reference list for
theReferenceData "key". The reference list is value data for the specified refDataValueHdr
(the value header for the recording object's reference list). The result of the search
is returned as the function result along with an offset and object ID. The meaning of the
returned key, objectID, and refShadowEntry are a function of the returned search status as
follows:
RefFound: The key was found.
key = internal (hardware) representation of the CMReference key.
objectID = object ID associated with the found key.
refShadowEntry = ptr to found shadow list entry (NULL if no shadow list).
offset = value data offset to the key.
RefNotFound: The key was not found.
key = internal (hardware) representation of the CMReference key.
objectID = undefined.
refShadowEntry = NULL.
offset = current size of the value data. This allows the caller
to append the new reference if necessary.
RefReadError: A read error has occurred and the error reporter returned. Caller must
not "go on".
Note, the returned key is the hardware (unsigned long) representation of the input
CMReference theReferenceData key. This is converted here to make it easier to work with.
So for convenience it is given back to the caller. However, the caller may have already
did this for the same reason. So, by convention, if the key is PASSED IN as 0, the
conversion is done here. If it is nozero, it is assumed the caller did it, and the key
is used as passed, i.e., it is assumed that theReferenceData was converted to key.
The Shadow List
===============
Under configuation option (CMSHADOW_LIST) a shadow list is created here. The "shadow
list" is a copy of the actual value data (thus it "shadows" the data - tricky name 'eh?).
All changes to the data are shadowed in the list. But the list is in internal (hardware)
format for the keys and object IDs and in memory to make these searches we do here more
efficient than reading it each time. Of course, we have to read it the first time.
Normally invertSerach is false and we search in the normal way. However, if invertSerach
is true, we invert the search to find a reference for the given object ID
*/
static RefSearchStatus CM_NEAR CM_PASCAL getReference(TOCValueHdrPtr refDataValueHdr,
CMReference theReferenceData,
CM_ULONG *offset,
CM_ULONG *key,
CMObjectID *objectID,
CMBoolean invertSearch,
RefDataShadowEntryPtr *refShadowEntry)
{
ContainerPtr container = refDataValueHdr->container;
CM_ULONG x, currKey, size = refDataValueHdr->size;
CMObjectID currObjectID;
RefSearchStatus searchStatus = RefNotFound;
void *ioBuffer = NULL;
jmp_buf getRefEnv;
#if CMSHADOW_LIST
ListHdrPtr refShadowList;
RefDataShadowEntryPtr r;
#endif
/* The user's key is a CMReference which are awkward to work with internally. So all */
/* manipulations of the key are done in internal (i.e., hardware) format. To avoid */
/* unnecessarily doing this conversion a second time if the caller already did it, */
/* the key can be passed as non-zero. Zero indicates we are to convert it here. */
if (!invertSearch)
if (*key == 0) /* if caller didn't supply key*/
CMextractData(container, theReferenceData, -4, key);/* ...convert key to internal */
/* If the shadow list already exists, search it now since we know it's in sync */
/* (hopefully) with the recording object's actual reference list value data. */
#if CMSHADOW_LIST
if (RefShadowList(refDataValueHdr) != NULL) { /* if shadow lists exists... */
r = (RefDataShadowEntryPtr)cmGetListHead(RefShadowList(refDataValueHdr));
*offset = 0; /* maintain value data offset */
while (r) { /* search the list... */
if (invertSearch) {
if (r->objectID == *objectID) {
*key = r->key;
*refShadowEntry = r; /* ...return shadow entry ptr */
return(RefFound); /* ...return key found status */
}
} else {
if (r->key == *key) { /* ...if the key is found... */
*objectID = r->objectID; /* ...return its object ID */
*refShadowEntry = r; /* ...return shadow entry ptr */
return(RefFound); /* ...return key found status */
}
}
r = (RefDataShadowEntryPtr)cmGetNextListCell(r); /* ...get next shadow entry */
*offset += sizeof(ReferenceData); /* ...account for value data */
}
*refShadowEntry = NULL; /* the key was not found */
return (RefNotFound);
}
/* If the shadow list doesn't exist for the recording value create it now. First the */
/* list header which is pointed to from the value's header. The pointer is the same */
/* field as the recording object pointer. It is a union to give it a more appropriate */
/* name. We always know the difference because the recording object's value header is */
/* uniquely typed. */
refShadowList = RefShadowList(refDataValueHdr) = (ListHdrPtr)CMmalloc(sizeof(ListHdr));
if (refShadowList == NULL) {
ERROR1(CM_err_NoRefShadowList, CONTAINERNAME);
return (RefReadError);
}
cmInitList(refShadowList);
#endif
/* There's nothing to do if there are no references. If we were called from */
/* CMSetReference(), it will create the first entry. */
if (size == 0) { /* if no data to read... */
*offset = 0; /* ...return current size */
*refShadowEntry = NULL; /* ...no shadow entry */
return (RefNotFound); /* ...return key not found */
}
/* Read in the reference data to build the shadow list this first time if the config */
/* allows it. All further entry modifications are done by the callers to shadow their */
/* respective operations to the recording value data in the list. While we're at it, */
/* we do the required search for the desired key. If we're not building the shadow */
/* list, we can break out of the loop as soon as we find the key. Otherwise, the */
/* entire list must be read in. */
/* Note, in a (crude) attempt to make this read and search more efficient (after all */
/* we have to read read value data here), we use buffered value I/O. */
/* See BufferIO.c for further details on how buffered value data I/O works. */
if (setjmp(getRefEnv)) { /* ...set for buffering errors*/
cmReleaseIOBuffer(ioBuffer); /* ...if longjmp taken to here*/
#if CMSHADOW_LIST
cmDeleteRefDataShadowList(refDataValueHdr);
#endif
ERROR1(CM_err_BadRefRead, CONTAINERNAME); /* ...we had an read error */
return (RefReadError); /* ...screw it */
}
ioBuffer = cmUseIOBuffer(container, RefsBufSize, (jmp_buf *)&getRefEnv); /*define bufr*/
if (ioBuffer == NULL) {
#if CMSHADOW_LIST
cmDeleteRefDataShadowList(refDataValueHdr);
#endif
return (RefReadError);
}
cmNewBufferedInputData(ioBuffer, refDataValueHdr, refDataValueHdr->size);
for (x = 0; x < size; x += 8) { /* find the reference (key)...*/
currKey = (CM_ULONG)GET4Direct(ioBuffer); /* ...read next key */
currObjectID = (CMObjectID)GET4(ioBuffer); /* ...its assoc. ID follows */
#if CMSHADOW_LIST
r = appendShadowListEntry(container, refShadowList, currKey, currObjectID);
if (r == NULL) {
cmDeleteRefDataShadowList(refDataValueHdr);
cmReleaseIOBuffer(ioBuffer);
return (RefReadError);
}
#endif
if (invertSearch) { /* we look for object ID */
if (currObjectID != *objectID) /* not found yet */
continue; /* then keep looking */
*key = currKey; /* found, get assoc. key */
} else { /* we look for key */
if (currKey != *key) /* not found yet */
continue; /* then keep looking */
*objectID = currObjectID; /* found, get assoc. object ID*/
}
searchStatus = RefFound; /* ...we found it! */
*offset = x; /* ...return value data offset*/
#if CMSHADOW_LIST
*refShadowEntry = r; /* ...set ptr if shadowing */
#else
*refShadowEntry = NULL; /* ...NULL if not shadowing */
#endif
break; /* ...found, we are done */
} /* for */ /* ...keep reading */
cmReleaseIOBuffer(ioBuffer); /* done searching and reading */
if (searchStatus == RefNotFound) { /* if the key wasn't found... */
*offset = size; /* ...ret end of list position*/
*refShadowEntry = NULL; /* ...no shadow entry */
}
return (searchStatus); /* return search status */
}
/*----------------------------------------------------------------*
| CMNewReference - define a "reference" to an object for a value |
*----------------------------------------------------------------*
Creates a "reference" to the referencedObject and places it in theReferenceData. The
(input) pointer to theReferenceData is returned. It is ASSUMED that this data will be
written as (part of) the value data for the specified value. The size of this data is
determined by the size of the CMReference type, i.e., sizeof(CMReference).
A side effect of this routine is the creation of a (private to the Container Manager)
object (for the first reference) that records unique object/reference associations for the
passed value. What is returned is a "key" that is recorded as the "reference" to allow
retrevial (e.g., using CMGetReferencedObject()) of the object (refNum) corresponding to a
reference (key).
Calling CMNewReference() for a already existing reference (key) simply sets
theReferenceData and returns its pointer.
Note, that CMNewReference() always defines theReferenceData key. However, CMSetReference()
can be called instead to define, or redefine, an ARBITRARY key (still of type CMReference)
to associate with an object. See CMSetReference() for further details.
*/
CMReference CM_PTR * CM_FIXEDARGS CMNewReference(CMValue value,
CMObject referencedObject,
CMReference CM_PTR theReferenceData)
{
ContainerPtr container;
ContainerPtr targetContainer;
ExitIfBadValue(value, NULL); /* validate value */
ExitIfBadObject(referencedObject, NULL); /* validate referencedObject */
container = ((TOCValueHdrPtr)value)->container->updatingContainer;
targetContainer = ((TOCValueHdrPtr)value)->container->targetContainer;
if (targetContainer != ((TOCObjectPtr)referencedObject)->container->targetContainer) {
ERROR2(CM_err_CantReference, CONTAINERNAMEx(container),
CONTAINERNAMEx(((TOCObjectPtr)referencedObject)->container));
return (NULL);
}
/* A CMObjectID for some hardware implementations may NOT be exactly 4 bytes (but it */
/* better be at least 4 bytes). A user's "reference" data is always 4 bytes because */
/* we define such data here as an object ID. We must take care to convert the */
/* internal, i.e., hardware, representation to that of a container representation. By */
/* share "luck" (yea right, want to buy a bridge?) we just happen to have a handler */
/* that does this conversion. */
CMformatData(container, theReferenceData, -4, &((TOCObjectPtr)referencedObject)->objectID);
/* Since we have defined the CMReference key here, we can call CMSetReference() just */
/* like the user to (re)define the object reference with this key. */
return (CMSetReference(value, referencedObject, theReferenceData));
}
/*--------------------------------------------------------------------*
| CMSetReference - (re)define a "reference" to an object for a value |
*--------------------------------------------------------------------*
This is similar to CMNewReference() except that here the caller defines the CMReference
key to associate with an object. The specified key must not be a nonzero value. The
(input) pointer to theReferenceData key is returned.
In all cases the specified CMReference key is associated with the specified
referencedObject. The associations are recorded in the private recording object for the
passed value. New references are recorded, and previously existing references (i.e.,
theReferenceData key matches one of the previously recorded keys) are CHANGED to assoicate
it with the (new) referencedObject.
The only difference between CMNewReference() and CMSetReference() is that with
CMNewReference(), the Container Manager defines the CMReference key, while with
CMSetReference() the caller defines the key. The net result is the same; the keys are
recorded in the value's recording object to define the association to the specified
referenced object.
Note, that multiple references to the SAME object can be recorded by passing different
keys (theReferenceData).
Once these associations are recorded, they may be counted, deleted, and accessed using
CMCountReferences(), CMDeleteReference(), and CMGetNextReference() respectively.
*/
CMReference CM_PTR * CM_FIXEDARGS CMSetReference(CMValue value,
CMObject referencedObject,
CMReference CM_PTR theReferenceData)
{
ContainerPtr container;
ContainerPtr targetContainer;
TOCObjectPtr refDataObject, refedObject;
TOCValueHdrPtr theValueHdr, refDataValueHdr;
CM_ULONG key, offset, orgSize, amountRead;
CMObjectID objectID;
RefDataShadowEntryPtr refShadowEntry;
ReferenceData refData;
CM_CHAR *tempBuf;
ExitIfBadValue(value, NULL); /* validate value */
ExitIfBadObject(referencedObject, NULL); /* validate referencedObject */
theValueHdr = (TOCValueHdrPtr)value;
container = theValueHdr->container->updatingContainer;
targetContainer = theValueHdr->container->targetContainer;
refedObject = (TOCObjectPtr)referencedObject;
if (targetContainer != refedObject->container->targetContainer) {
ERROR2(CM_err_CantReference, CONTAINERNAMEx(targetContainer),
CONTAINERNAMEx(refedObject->container));
return (NULL);
}
/* The list of references is recorded in a private object associated with the user's */
/* value (header). The reference data object is "tied" to the user's value through a */
/* pointer in the user's value header. Of course, the first such reference must */
/* create the recording object and tie it to the user's value header. */
/* Since an object is created, the user would see it if if s/he walked the TOC with */
/* CMGetNextObject(). But this is to be a private object. So to stop the user from */
/* seeing it, it is unlinked from the object master chain which is used by the */
/* CMGetNextObject(). */
/* Note that a value move moves a value header and hence the recording object will */
/* "go along for the ride". Property and value deletes, however, must make sure to */
/* delete this object. */
if (!HasRefDataObject(theValueHdr)) { /* if 1st ref for this value... */
/* if the value is not part of the updating container but in some target container */
/* then the reference is not attached to the value since the TOC in the target */
/* will not be updated, and since we only have an update value on the updating */
/* container, there is no place to attach the reference in the TOC, so what we do */
/* is a kludge, we delete the value and then insert it back into updating container.*/
/* Now we can attach the reference to new value. However, we do not want to really */
/* delete the value because if we create a new value header it would have a */
/* different refnum. Also we need to copy the data from the target. Instead we just */
/* record deleted in the touch list by using cmTouchDeletedValue so it would show */
/* up in the update value, and then we just declare it to belong to the updating */
/* container, effectively moving it to the updating container */
if (theValueHdr->container != container) {
orgSize = theValueHdr->size;
if (orgSize) {
/* save a copy of the original data */
tempBuf = (CM_CHAR *)CMmalloc(orgSize); /* DMc - orgSize, not sizeof(orgSize) */
if (tempBuf == NULL) return (NULL); /* exit if failure */
amountRead = CMReadValueData((CMValue)theValueHdr, tempBuf, 0, orgSize);
if (amountRead != orgSize) {
CMfree(tempBuf); /* exit if fail to read */
return (NULL);
}
CMDeleteValueData((CMValue)theValueHdr, 0, orgSize);
}
/* What we are doing is to delete the old value from the old container and then */
/* insert it into the new container, but then we would change theValueHdr. */
/* So we just delete the data and cheat by changing the container */
cmTouchDeletedValue(theValueHdr, theValueHdr->theProperty->theObject);
theValueHdr->container = container;
if (orgSize) {
/* write back the original data */
CMInsertValueData((CMValue)theValueHdr, tempBuf, 0, orgSize);
CMfree(tempBuf);
}
}
refDataObject = cmDefineObject(container, /* ...create recording object */
container->nextUserObjectID,
CM_StdObjID_ObjReferences,
CM_StdObjID_ObjRefData,
NULL,
container->generation, 0,
(ObjectObject | ProtectedObject),
&refDataValueHdr);
if (refDataObject == NULL) return (NULL); /* exit if failure */
cmUnlinkObject(container->toc, refDataObject); /* unlink from master chain */
refDataObject->useCount = 1; /* initial use of this object */
IncrementObjectID(container->nextUserObjectID); /* set to use next available ID */
refDataValueHdr->valueFlags |= ValueProtected; /* protect from user fiddling */
RefDataObject(theValueHdr) = refDataObject; /* link value to the object */
} else { /* use existing obj if >1st time*/
refDataValueHdr = getRecordingValueHdr(theValueHdr);/* get recording obj's value hdr*/
if (refDataValueHdr == NULL) return (NULL); /* something went wrong? */
}
container = refDataValueHdr->container;
CMextractData(container, theReferenceData, -4, &key); /* don't allow a key of 0 */
if (key == 0) {
ERROR1(CM_err_ZeroRefKey, CONTAINERNAME);
return (NULL);
}
/* Search for theReferenceData key in the recording object... */
switch (getReference(refDataValueHdr, theReferenceData, &offset, &key, &objectID, false, &refShadowEntry)) {
case RefReadError: /* If there was some kind of read error, and the error reporter */
default: /* returned, just exit... */
return (NULL);
case RefFound: /* If theReferenceData key was found and the associated ID does */
/* NOT need to be changed, just return the reference key. On the */
/* the other hand, if the key does need changing, then we FALL */
/* THROUGH to the "not found" case to rerecord the list entry as */
/* if it's a new entry. The offset is set to the proper place. */
if (refedObject->objectID == objectID) /* if same ID... */
return ((CMReference *)theReferenceData); /* ...return ref. */
case RefNotFound: /* If theReferenceData key was not found (or we must change its */
/* associated ID if it was found) then we (re)write the value */
/* data entry with theReferenceData key and its (new) ID. The ID */
/* follows the key in the data (4 bytes each). */
refDataValueHdr->valueFlags &= ~ValueProtected;/* allow write */
CMformatData(container, refData.key, -4, theReferenceData);
CMformatData(container, refData.objectID, 4, &refedObject->objectID);
CMWriteValueData((CMValue)refDataValueHdr, (CMPtr)&refData, offset, 8);
refDataValueHdr->valueFlags |= ValueProtected; /* reprotect value*/
/* Create a new shadow list entry or change data in found entry. */
/* All this info was generated by getReference() in the switch */
/* statement above. */
#if CMSHADOW_LIST
if (refShadowEntry == NULL) { /* new entry */
refShadowEntry = appendShadowListEntry(container, RefShadowList(refDataValueHdr), key, refedObject->objectID);
if (refShadowEntry == NULL) {
cmDeleteRefDataShadowList(refDataValueHdr);
return (NULL);
}
} else /* change entry */
refShadowEntry->objectID = refedObject->objectID;
#endif
return ((CMReference *)theReferenceData); /* return ref. */
} /* switch */
}
/*---------------------------------------------------------------------*
| getReferencedObject - retrieve a object (refNum) from a "reference" |
*---------------------------------------------------------------------*
Converts an object "reference", created by CMNewReference() or CMSetReference(), back to
an object refNum, from theReferenceData key is (assumed) supplied from the specified
value. If theReferenceData key is not found, NULL is returned. However, if the key is
found but the referenced object is NOT found, then either returns NULL an error depending
on the setting of reportUndefReferenceError.
*/
static CMObject getReferencedObject(CMValue value, CMReference CM_PTR theReferenceData,
CMBoolean reportUndefReferenceError)
{
ContainerPtr container;
TOCObjectPtr refedObject;
TOCValueHdrPtr theValueHdr, refDataValueHdr;
CM_ULONG offset, key = 0;
CMObjectID objectID;
RefDataShadowEntryPtr refShadowEntry;
CM_CHAR idStr[15];
ExitIfBadValue(value, NULL); /* validate value */
theValueHdr = (TOCValueHdrPtr)value;
/* If there is NO recording object, there are no references. This is treated as a */
/* "not found" condition. */
refDataValueHdr = getRecordingValueHdr(theValueHdr); /* get recording value hdr */
if (refDataValueHdr == NULL) return (NULL); /* "not found" if no object */
container = refDataValueHdr->container;
/* Search for theReferenceData key in the recording object... */
switch (getReference(refDataValueHdr, theReferenceData, &offset, &key, &objectID,
false, &refShadowEntry)) {
case RefReadError: /* If there was some kind of read error, and the error reporter */
/* returned, just exit... */
return (NULL);
case RefFound: /* If theReferenceData key was found, use the associated ID to */
/* find the object in the TOC. It must be found or it's an error.*/
/* If it is, return the object's refNum and treat it as a "use" */
/* (i.e., increment the object's use count). */
refedObject = cmFindObject(theValueHdr->container->updatingContainer->toc,
objectID);/* lookup ID*/
if (refedObject == NULL) /* ...oops! */
if (reportUndefReferenceError)
ERROR2(CM_err_UndefReference, cmltostr(objectID, 1, false, idStr), CONTAINERNAME)
else
return (NULL);
++refedObject->useCount; /* count use*/
return ((CMObject)refedObject);
case RefNotFound: /* If theReferenceData key was not found just exit... */
default:
return (NULL);
} /* switch */
}
/*-----------------------------------------------------------------------*
| CMGetReferencedObject - retrieve a object (refNum) from a "reference" |
*-----------------------------------------------------------------------*
Converts an object "reference", created by CMNewReference() or CMSetReference(), back to
an object refNum, from theReferenceData key is (assumed) supplied from the specified
value. If theReferenceData key is not found, NULL is returned. However, it is an error
if the key is found but the referenced object is NOT found.
Just call getReferencedObject with reportUndefReferenceError true;
*/
CMObject CM_FIXEDARGS CMGetReferencedObject(CMValue value, CMReference CM_PTR theReferenceData)
{
return getReferencedObject(value, theReferenceData, true);
}
/*-------------------------------------------------------------------------*
| CMGetReferenceForObject - retrieve a "reference" from a object (refNum) |
*-------------------------------------------------------------------------*
This is the invert of CMGetReferencedObject.
Given an object refNum, find theReferenceData key associated with this object ID.
*/
CMReference CM_PTR * CM_FIXEDARGS CMGetReferenceForObject(CMValue value,
CMObject referencedObject,
CMReference CM_PTR theReferenceData)
{
ContainerPtr container;
ContainerPtr targetContainer;
TOCValueHdrPtr theValueHdr, refDataValueHdr;
CM_ULONG offset, key = 0;
CMObjectID objectID;
RefDataShadowEntryPtr refShadowEntry;
ExitIfBadValue(value, NULL); /* validate value */
ExitIfBadObject(referencedObject, NULL); /* validate referencedObject */
objectID = ((TOCObjectPtr)referencedObject)->objectID;
theValueHdr = (TOCValueHdrPtr)value;
container = theValueHdr->container->updatingContainer;
targetContainer = theValueHdr->container->targetContainer;
if (targetContainer != ((TOCObjectPtr)referencedObject)->container->targetContainer) {
ERROR2(CM_err_CantReference, CONTAINERNAMEx(container),
CONTAINERNAMEx(((TOCObjectPtr)referencedObject)->container));
return (NULL);
}
/* If there is NO recording object, there are no references. This is treated as a */
/* "not found" condition. */
refDataValueHdr = getRecordingValueHdr(theValueHdr); /* get recording value hdr */
if (refDataValueHdr == NULL) return (NULL); /* "not found" if no object */
/* Search for theReferenceData key in the recording object... */
switch (getReference(refDataValueHdr, theReferenceData, &offset, &key, &objectID,
true, &refShadowEntry)) {
case RefReadError: /* If there was some kind of read error, and the error reporter */
/* returned, just exit... */
return (NULL);
case RefFound: /* If object was found, use the associated ID to */
/* find the object in the TOC. It must be found or it's an error.*/
CMformatData(refDataValueHdr->container, theReferenceData, -4, &key);
return ((CMReference *)theReferenceData);/* give back ptr to ref */
case RefNotFound: /* If theReferenceData key was not found just exit... */
default:
return (NULL);
} /* switch */
}
/*------------------------------------------------------------*
| CMDeleteReference - delete an object/reference association |
*------------------------------------------------------------*
Deletes a single object "reference", created by CMNewReference() or CMSetReference()
assocated with the theReferenceData key (assumed) supplied from the specified value.
The value's recording object's object/reference association list is searched for the
specified theReferenceData key. If it is found, the association is removed. If it is
not found this routine does nothing. Thus it is NOT considered an error if the
theReferenceData key is not found, or if found, that the associated object exist.
Note, if all the references for the value's recording object are deleted, the recording
object itself is deleted.
*/
void CM_FIXEDARGS CMDeleteReference(CMValue value, CMReference CM_PTR theReferenceData)
{
ContainerPtr container;
TOCValueHdrPtr theValueHdr, refDataValueHdr;
CM_ULONG offset, key = 0;
CMObjectID objectID;
RefDataShadowEntryPtr refShadowEntry;
ExitIfBadValue(value, CM_NOVALUE); /* validate value */
theValueHdr = (TOCValueHdrPtr)value;
container = theValueHdr->container->targetContainer;
/* If there is NO recording object, there are no references. This is treated as a */
/* "not found" condition, so there's nothing to delete. */
refDataValueHdr = getRecordingValueHdr(theValueHdr); /* get recording value hdr */
if (refDataValueHdr == NULL) return; /* ...nothing to delete */
/* Search for theReferenceData key in the recording object... */
switch (getReference(refDataValueHdr, theReferenceData, &offset, &key, &objectID,
false, &refShadowEntry)) {
case RefReadError: /* If there was some kind of read error, and the error reporter */
/* returned, just exit... */
return;
case RefFound: /* If theReferenceData key was found, use its offset to delete */
/* the reference (data). If there are no more references in the */
/* recording object, the object itself is deleted. */
/* the reference object is deleted if is is empty. However it is possible that during
updating, the reference object is deleted but we still point to it, until that problem
is solved, we leave it as object with empty value
if (refDataValueHdr->size == 8) { // if single item list
RefDataObject(theValueHdr)->objectFlags &= ~ProtectedObject;
CMDeleteObject((CMObject)RefDataObject(theValueHdr)); // delete
RefDataObject(theValueHdr) = NULL; // ...break link
}
else { // deprotect it during the Delete process
refDataValueHdr->valueFlags &= ~ValueProtected;
CMDeleteValueData((CMValue)refDataValueHdr, (CMCount)offset, 8);
refDataValueHdr->valueFlags |= ValueProtected;
}
*/
refDataValueHdr->valueFlags &= ~ValueProtected;
CMDeleteValueData((CMValue)refDataValueHdr, (CMCount)offset, 8);
refDataValueHdr->valueFlags |= ValueProtected;
#if CMSHADOW_LIST
deleteShadowListEntry(refDataValueHdr, refShadowEntry);
#endif
return;
case RefNotFound: /* If theReferenceData key was not found just exit... */
default:
return;
} /* switch */
}
/*----------------------------------------------------------------------------------*
| CMCountReferences - return number of references recorded for the specified value |
*----------------------------------------------------------------------------------*
Returns the total number of references recorded by CMNewReference() or CMSetReference()
for the specified value.
*/
CMCount CM_FIXEDARGS CMCountReferences(CMValue value)
{
ContainerPtr container;
TOCValueHdrPtr theValueHdr, refDataValueHdr;
ExitIfBadValue(value, 0); /* validate value */
theValueHdr = (TOCValueHdrPtr)value;
container = theValueHdr->container->targetContainer;
/* If there is NO recording object, there are no references... */
refDataValueHdr = getRecordingValueHdr(theValueHdr); /* get recording value hdr */
if (refDataValueHdr == NULL) return (0); /* ...no references */
return (refDataValueHdr->size / 8); /* 8 bytes per reference */
}
/*--------------------------------------------------------------------------*
| getAdjacentReference - get the adjacent reference referred to by a value |
*--------------------------------------------------------------------------*
This routine returns the adjacent reference (key) following/preceding the currReferenceData
key for the specified value.
If currReferenceData is 0, then the first/last object reference key is returned. If
currReferenceData is not 0, the next/previous reference key in sequence is returned. In both cases
the currReferenceData is CHANGED to the next/previous reference and the pointer to it returned as
the function result. NULL is returned and currReferenceData is undefined if there are no
more references following/preceding currReferenceData.
Note, the object refNum corresponding to each returned reference key mey be retrieved by
calling CMGetReferencedObject().
*/
static CMReference CM_PTR * CM_NEAR getAdjacentReference(CMValue value,
CMReference CM_PTR currReferenceData,
CMBoolean forward)
{
ContainerPtr container;
TOCValueHdrPtr theValueHdr, refDataValueHdr;
CM_ULONG currKey;
ExitIfBadValue(value, NULL); /* validate value */
theValueHdr = (TOCValueHdrPtr)value;
/* If there is NO recording object, there are no references... */
refDataValueHdr = getRecordingValueHdr(theValueHdr); /* get recording value hdr */
if (refDataValueHdr == NULL) return (NULL); /* ...no references */
container = refDataValueHdr->container;
/* If the passed key is 0, treat this as the first/last reference. The "next" */
/* reference position is maintained in the recording value's refCon (it's available */
/* -- no one else is using it in the recording value header). */
CMextractData(container, currReferenceData, 4, &currKey); /* convert key to internal */
/* From this point on we do the "get next" operations differently depending on */
/* whether a shadow list exists for the recording object's value data list. It need */
/* not exist if getReference() was never called or we're configured not to have a */
/* shadow list. If we do have the list, we search it, rather than reading the data */
/* explicitly which is what we have to do if the list does not exist. */
#if CMSHADOW_LIST
if (RefShadowList(refDataValueHdr) != NULL) { /* if shadow lists exists... */
/* When using the shadow list, the recording value's refCon is a pointer to the */
/* next shadow list entry. When the key is 0 we know to init the pointer to the */
/* first shadow list entry. */
if (currKey == 0) /* if first time... */
if (forward) /* first item if we want next, last item if we want prev */
refDataValueHdr->valueRefCon = (CMRefCon)cmGetListHead(RefShadowList(refDataValueHdr));
else
refDataValueHdr->valueRefCon = (CMRefCon)cmGetListTail(RefShadowList(refDataValueHdr));
/* If there is no "next" list entry, there are no more references... */
if (refDataValueHdr->valueRefCon == NULL)
return (NULL);
/* The "next" pointer from the previous call is the "current" pointer for this call.*/
/* Use it to convert the shadow list key of the entry pointed to to a CMReference. */
/* This is what we will give back to the user. */
CMformatData(container, currReferenceData, 4,
&((RefDataShadowEntryPtr)(refDataValueHdr->valueRefCon))->key);
/* Update the pointer to the "next" shadow list entry for the next time around... */
if (forward)
refDataValueHdr->valueRefCon = (CMRefCon)(cmGetNextListCell(refDataValueHdr->valueRefCon));
else
refDataValueHdr->valueRefCon = (CMRefCon)(cmGetPrevListCell(refDataValueHdr->valueRefCon));
return ((CMReference *)currReferenceData); /* give back ptr to reference */
} /* using shadow list */
#endif
/* If there is no shadow list, we have to do things the "hard way". We have to */
/* explicitly read the recording object's value data list entries in succession. Here */
/* the refCon is still a "pointer" to the next shadow list entry. But unlike the */
/* shadow list case, the refCon takes the form of the next value data offset to be */
/* used to read in the next entry. As usual, a input key of 0 signals that this is */
/* the first call and thus to init the pointer to offset 0. */
if (currKey == 0) /* if first time... */
if (forward)
refDataValueHdr->valueRefCon = (CMRefCon)0; /* init value data offset */
else
refDataValueHdr->valueRefCon = (CMRefCon)(refDataValueHdr->size - sizeof(ReferenceData));
/* If the current offset is at the end of the data, there are no more references... */
if (forward) {
if ((CM_ULONG)refDataValueHdr->valueRefCon >= refDataValueHdr->size)
return (NULL);
}
else {
if ((CM_ULONG)refDataValueHdr->valueRefCon < 0)
return (NULL);
}
/* Read the next reference at the current offset and bump the offset for the next */
/* time. Note, we only have to read the key, but we have to bump the offset by the */
/* entry size. Also, since the key is already in CMReference format, we can return */
/* it to the caller as is. */
if (CMReadValueData((CMValue)refDataValueHdr, (CMPtr)currReferenceData,
(CM_ULONG)refDataValueHdr->valueRefCon, sizeof(CMReference)) != sizeof(CMReference)) {
ERROR1(CM_err_BadRefRead, CONTAINERNAME);
return (NULL);
}
if (forward)
refDataValueHdr->valueRefCon = (CMRefCon)((CM_ULONG)refDataValueHdr->valueRefCon
+ sizeof(ReferenceData));
else
refDataValueHdr->valueRefCon = (CMRefCon)((CM_ULONG)refDataValueHdr->valueRefCon
- sizeof(ReferenceData));
return ((CMReference *)currReferenceData); /* give back ptr to reference */
}
/*--------------------------------------------------------------------*
| CMGetNextReference - get the next reference referred to by a value |
*--------------------------------------------------------------------*
Just call getAdjacentReference with forward = true.
*/
CMReference CM_PTR * CM_FIXEDARGS CMGetNextReference(CMValue value,
CMReference CM_PTR currReferenceData)
{
return getAdjacentReference(value, currReferenceData, true);
}
/*-------------------------------------------------------------------------*
| CMGetPrevtReference - get the previous reference referred to by a value |
*-------------------------------------------------------------------------*
Just call getAdjacentReference with forward = false.
*/
CMReference CM_PTR * CM_FIXEDARGS CMGetPrevReference(CMValue value,
CMReference CM_PTR currReferenceData)
{
return getAdjacentReference(value, currReferenceData, false);
}
/*------------------------------------------------------------------------*
| CMKeepObject - protect this object and associated object from deletion |
*------------------------------------------------------------------------*
Protect this object. Follow all the reference from this object and protect those objects
as well. This is done recursively so all related object are protected. Once we make this
call, then on closing all non-protected object are consider to be available for garbage
collection and will be deleted.
*/
void CM_FIXEDARGS CMKeepObject(CMObject theObject)
{
CMProperty currProperty;
CMValue currValue;
CMReference currRef;
CMObject refObj;
TOCObjectPtr typeObject;
ExitIfBadObject(theObject, CM_NOVALUE); /* validate theObject */
/* only need to do it if it is not alreay protected */
/* also don't do it if this is only open for reading only */
if (((((TOCObjectPtr)theObject)->objectFlags & ProtectedObject) == 0) &&
(((TOCObjectPtr)theObject)->container->updatingContainer->useFlags & kCMWriting)) {
((TOCObjectPtr)theObject)->objectFlags |= ProtectedObject;
/* Once this is call, the container will be garbage collected on closing */
((TOCObjectPtr)theObject)->container->updatingContainer->deleteGarbage = true;
/* go and located all the references and protected object referenced as well */
currProperty = NULL;
do {
currProperty = CMGetNextObjectProperty(theObject, currProperty);
if (currProperty) {
/* We use this property, so protect it so it won't be deleted */
((TOCObjectPtr)currProperty)->objectFlags |= ProtectedObject;
currValue = NULL;
do {
currValue = CMGetNextValue(theObject, currProperty, currValue);
if (currValue) {
/* We use this type, so protect it so it won't be deleted */
typeObject = cmFindObject(((TOCObjectPtr)theObject)->container->toc,
((TOCValueHdrPtr)currValue)->typeID);
if (typeObject)
typeObject->objectFlags |= ProtectedObject;
if (CMCountReferences(currValue)) { /* we have references */
memset(&currRef, 0, sizeof(CMReference));
while (*CMGetNextReference(currValue, currRef)) { /* loop through refs */
refObj = getReferencedObject(currValue, currRef, false);
if (refObj) {
CMKeepObject(refObj); /* recursively protect them all */
CMReleaseObject(refObj); /* decr use count incr by CMGetReferen... */
}
else /* There is no such reference object, delete it */
CMDeleteReference(currValue, currRef);
}
}
}
} while (currValue); /* iterated through all types */
}
} while (currProperty); /* iterated through all properties */
}
}
CM_END_CFUNCTIONS