OSF DCE SIG S. Strange (Digital) Request For Comments: 51.1 February 1994 DFS SOURCE CODE CLEANUP TO SUPPORT BOTH 32-BIT AND 64-BIT ARCHITECTURES 1. INTRODUCTION This paper describes code clean-up changes made to the DCE/DFS V1.0.2a code base to support architectures with either 32-bit-wide or 64-bit-wide file offsets. This work is being done as part of the port of DFS to DEC OSF/1 for Alpha AXP systems. The result of these modifications does not affect the operation of the code on 32-bit architectures, but does make DCE/DFS fully 64-bit clean. However, these changes do not address the potential problems between 32-bit and 64-bit machines when files greater than 4GB in size are accessed. Interoperability issues between 32-bit and 64-bit architectures are discussed in [RFC 51.0]. 2. PROBLEM STATEMENT The DFS code was designed with 64-bit support in mind. However, the data structures do not allow for straightforward mapping of 64-bit quantities to native types of any architecture other than the assumed default of a 32-bit big-endian machine. Efficiency considerations make it desirable to manipulate hypers and token ranges with single 64-bit integer variable on platforms that have these types available. A natural way to create platform-specific types in a DCE RPC environment is through the use of the [represent_as] feature of the DCE Interface Definition Language (IDL). This feature allows data to be represented on the local machine as a different type than in the over-the-wire protocol. Routines supplied to IDL perform the necessary data conversions to support this alternate local representation. Unfortunately, due to the way hypers and token ranges are defined and used in the DFS code base, [represent_as] cannot be used without many additional changes to the code base. To see this, consider the current definitions for 64-bit quantities: 2.1. The Hyper Type A generic data type for over-the-wire 64-bit quantities is defined as a structure as follows: Strange Page 1 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 typedef struct afsHyper { unsigned32 high; unsigned32 low; } afsHyper; The problem here is that much of the DFS code is written to directly access the elements of the afsHyper structure (e.g., x = token.low), so afsHyper cannot be declared as a native 64-bit type without changing all places in the code where hypers are referenced in this way. What makes the situation worse is that there are multiple definitions of hyper-like types across the major DFS components. Even though they contain exactly the same structure elements, the ANSI C compiler will consider them different types and accordingly will disallow assigning a variable of one hyper type to a variable of a different hyper type. Some of these problems must have been foreseen by the original authors of the DFS code, because some C macros exist for handling access to hyper types. If used properly, these macros can serve to abstract the precise definition of a hyper from the way in which it is accessed in the code. Unfortunately, these macros are not used consistently in the code. Also, more macros need to be defined in order to handle every hyper access cleanly. 2.2. Token Ranges To support the storage of 64-bit-wide byte ranges, a token is defined as follows: typedef struct afsToken { afsHyper tokenID; unsigned32 expirationTime; afsHyper type; unsigned32 beginRange; unsigned32 endRange; unsigned32 beginRangeExt; unsigned32 endRangeExt; } afsToken; where beginRangeExt and endRangeExt are the extensions to (the upper 32 bits of) beginRange and endRange, respectively. It is desirable that these 64-bit quantities be represented as native types on platforms with a 64-bit integer type as it makes arithmetic with local 64-bit file offsets far more simple and efficient. The problems associated with hypers in section 2.1 apply to token ranges as well. Direct references to 32-bit components of the token range should be changed to allow 64-bit platforms to represent the begin and end range boundaries as native 64-bit variables. Also, there are multiple definitions for token types in the base DFS code. Tkm_token_t is defined and used interchangeably with afsToken, even Strange Page 2 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 though they differ slightly in definition. The base DFS code does not currently make use of any C macros to access token ranges. 2.3. Record Lock Ranges The over-the-wire structure for record locks is defined as follows: typedef struct afsRecordLock { signed16 l_type; signed16 l_whence; unsigned32 l_start_pos; unsigned32 l_end_pos; unsigned32 l_pid; unsigned32 l_sysid; unsigned32 l_fstype; unsigned32 l_start_pos_ext; unsigned32 l_end_pos_ext; } afsRecordLock; where l_start_pos_ext and l_end_pos_ext are the 64-bit extensions to l_start_pos and l_end_pos, respectively. Note that these extension fields were originally named l_spare0 and l_spare1, but have been changed to support 64-bit locking in the DFS protocol. As with the token ranges, it is desirable that the 64-bit lock ranges be represented as a native 64-bit type on machines that support one. 3. STRATEGY The strategy to solve the problems described above is to use the [represent_as] feature of DCE IDL to define a single hyper type and token type for a given platform. All the base code is modified so that variables of these types are accessed only through the use of a set of standard macros. The macro set is defined on a per-platform basis, and the base DFS code is compiled against the appropriate set. A number of macros are added to the existing ones so that each type of variable access can be handled cleanly. As an example 64-bit system, consider the Alpha AXP architecture. The Alpha AXP C compiler considers a long to be 64 bits, while an int is 32 bits wide. Thus the hyper structure is represented locally as an unsigned long on Alpha AXP systems. The user-supplied [represent_as] translation routines effectively swap the upper and lower halves of a hyper whenever it passes through the RPC code. The afsToken structure is represented locally by a new token_t type. The token_t type is a structure similar to afsToken, except that the beginRange and endRange elements are defined as hypers, and the beginRangeExt and endRangeExt elements are eliminated: Strange Page 3 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 typedef struct local_token { hyper tokenID; u_int32 expirationTime; hyper type; hyper beginRange; hyper endRange; } token_t; Because the definition of hyper is defined on each platform, this definition for a token type is completely general and can be used on all architectures. Because a hyper is really an unsigned long on Alpha AXP systems, this new definition for tokens allows us to perform arthmetic operations between file offsets and token ranges directly. The afsRecordLock structure is represented locally by a new recordLock_t type. As with the token_t type, the 64-bit quantities in the new structure are represented by hypers: typedef struct local_RecordLock { int16 l_type; int16 l_whence; hyper l_start_pos; hyper l_end_pos; u_int32 l_pid; u_int32 l_sysid; u_int32 l_fstype; } recordLock_t; For the same reasons given for the token_t type, this new local definition for a record lock structure is general for all platforms. 4. DESCRIPTION OF CODE CHANGES The changes made to the base DFS code to access hypers and tokens through macros makes it easy for any vendor to use [represent_as] in a manner appropriate to their architecture. Note that the code changes do not necessitate the use of [represent_as] for hypers, but do require that [represent_as] be used to convert token_t and recordLock_t. This requirement is a result of using the hyper type to represent token and lock ranges in token_t and recordLock_t. Although we do not believe the [represent_as] translation routines will significantly affect overall performance, it would not be difficult to define and use an additional set of macros to handle token and lock ranges to eliminate this requirement on 32-bit systems. Whether [represent_as] is used or not, a set of 32-bit- platform hyper-handling macros can be used to generate executable code that is functionally equivalent to that generated before the changes were made. Strange Page 4 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 The code and definitions to support the use of IDL [represent_as] for hypers and tokens were placed in the ./file/config directory. The local definitions and translation routines have been written for the Alpha AXP architecture, and can therefore serve as an example of how the DCE IDL [represent_as] feature can be used. A brief description of the files added and modified follows: (a) local_64bit_types.h (new file): Contains the definitions for local data representation of hypers, tokens, and record locks. (b) local_64bit_xlate.c (new file): Provides routines to IDL for translation between local 64-bit data types and network data types, as well as "free" routines for 64-bit local and network objects. These routines end up in the libcommondata.a library. (c) stds.h: Provides macros that are used to access hypers in a consistent and transparent manner. Two sets of the macros are provided -- one that assumes the original definition of hyper, and one that assumes a hyper is 64-bit long. The definition of a preprocessor constant, HAS_64BIT_SCALAR_TYPE, determines which set of macros is used. (d) common_data.acf: The [represent_as] directives for hypers and tokens can be found in the block of this file describing the interface common_data. The changes to the base DFS code to support the [represent_as] definitions consist of two major pieces, as well as a significant amount of minor code clean-up. The first, and by far largest set of changes involved making use of hyper macros consistently wherever hypers are accessed in the code. This in turn required that there be only one definition for the local representation of hypers. (The original code defines afsHyper and hyper identically, but because they are separate definitions, problems arise when an object of one type is assigned to the value of an object of the other type.) This single definition is in local_64bit_types.h. All previous occurances of afsHyper and struct afsHyper in the base code have changed to hyper. The second set of changes involves token ranges. The new local definition of a token defines beginRange and endRange as hypers, so all references to these ranges must now use the hyper macros. All previous occurances of afsToken or struct afsToken have changed to token_t. The tkc code had been using a different but similar token structure, tkm_token (defined in ./file/tkm/tkm_tokens.h), instead of afsToken. tkm_token defines very similar elements as afsToken, but uses slightly different names: Strange Page 5 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 typedef struct tkm_token { tkm_tokenID_t tokenID; int expiration; tkm_tokenSet_t type; int startPosition; int endPosition; int startPositionExt; int endPositionExt; } tkm_token_t; The use of this structure was abandoned for consistency, therefore all previous occurances of tkm_token and struct tkm_token have also changed to token_t. Note that this requires changes to token structure element names used in the code. 4.1. Synopsis of Modifications The following definitions have been removed from the base DFS code: ===================================================================== Definition: | Defined in file: | Replaced by: --------------+-----------------------+------------------------------ tkm_token_t | ./tkm/tkm_tokens.h | token_t AFS_h* macros | ./config/common_def.h | equivalent h* macro (stds.h) hyper | ./config/stds.h | new defn. of hyper ===================================================================== The following definitions have been added to the base DFS code: ===================================================================== Definition: | Defined in file: | Replaces: --------------+-------------------------------+---------------------- hyper | ./config/local_64bit_types.h | afsHyper, old defn. | | of hyper | | ([represent_as]) token_t | ./config/local_64bit_types.h | afsToken, tkm_token_t | | (locally only, | | [represent_as]) recordLock_t | ./config/local_64bit_types.h | afsRecordLock | | ([represent_as]) h* macros for | ./config/stds.h | .low- and .high-style handling | (see stds.h for details) | accesses to hypers hyper | | ===================================================================== The following changes have been made to existing definitions: Strange Page 6 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 ===================================================================== Definition: | Defined in file: | Changed to: ----------------------+----------------------------+----------------- expiration | ./tkm/tkm_tokens_private.h | expirationTime field in | | tkm_internalToken_t | | ===================================================================== (This change is made to be consistent with the corresponding field in token_t.) 5. LIST OF COMMENTS RECEIVED The following is a list of comments and suggestions already received on the DFS code base changes described in this paper, primarily from the developers at Transarc. (a) It has been suggested that the definitions in local_64bit_types.h be moved to an existing OSI header file. The files were initially arranged as described above so as to keep the [represent_as] conversion routines, hyper macro definitions, and 64-bit type definitions all in one place. I am open to suggestions for moving the definitions to more appropriate files. In particular, should the hyper macros in stds.h be moved to an OSI header file? Perhaps the best approach would be to place these macros in ./file/osi//.h. (b) The names chosen for the new types, such as hyper and token_t, could be changed to minimize the possibility of type name conflicts (namespace pollution). The specific suggestions for these two types were osi_token_t and tkm_token_t. If there is consensus that this is important, the changes could be made during the merge with the OSF code base. (c) One of the issues not addressed in this paper was the definition of standards for designating constants that are 64- bit quantities. The suggestion is to use an OSI macro such as OSI_HYPER_CONST(c) to handle this. Similarly, format strings for printf could be defined to deal with printing 64-bit variables. For example, OSI_HYPER_CONV_SPEC could be defined as "%ld" or "%lld", depending on the compiler. (d) Transarc had concerns over the use of the macros to compare longs and hypers, and to convert between longs and hypers. On 32-bit systems, the upper 32 bits of hypers are ignored, while on 64-bit systems they are not. This is a problem that primarily must be dealt with on 32-bit systems, and was therefore not a part of our initial 64-bit support work for DEC OSF/1. This issue should be resolved with the code changes Strange Page 7 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 provided in association with [RFC 51.0]. APPENDIX A. DEFINITIONS OF HYPER-HANDLING MACROS The following is an excerpt of ./file/config/stds.h. It shows two sets of definitions of the hyper-handling macros: one that is compatible with the original 32-bit DFS core code, and one that was designed for the Alpha AXP architecture, which supports a native 64- bit type. #ifndef HAS_64BIT_SCALAR_TYPE /* ************************************************* * 32-bit versions of the hyper-handling macros. * ************************************************* */ #define MAX_OFFSET 0x7fffffff /* * Macros to convert longs to and from hypers * Note that the 32-bit versions of these macros ignore * the .high portion */ #define htolong(l, h) ((l) = (h).low) #define hfromlong(h, l) (((h).low = (l)), ((h).high = 0)) #define horinlong(a, l) ((a).low |= ((l) & 0xffffffffL)) #define hretlong(a) ((a).low) /* * usage of hcmp: * (a b) * can be expressed as: * (hcmp(a, b) 0) * where is one of: * < <= == > >= */ #define hcmp(a,b) ((a).high < (b).high ? -1 : \ ((a).high > (b).high ? 1 : \ ((a).low < (b).low ? -1 : \ ((a).low > (b).low ? 1 : 0)))) /* as hcmp(), but compare a hyper to a long instead of hyper to hyper */ #define hcmplong(h, l) ((h).high > 0 ? 1 : \ (h).low < (l) ? -1 : \ ((h).low > (l) ? 1 : 0)) #define hsame(a,b) ((a).low == (b).low && (a).high == (b).high) #define hiszero(a) ((a).low == 0 && (a).high == 0) #define hisones(a) ((a).low == (~(0)) && (a).high == (~(0))) Strange Page 8 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 #define hfitsin32(a) ((a).high == 0) #define hset(a,b) ((a) = (b)) #define hzero(a) ((a).low = 0, (a).high = 0) #define hones(a) ((a).low = 0xffffffff, (a).high = 0xffffffff) #define hget32(i,a) ((i) = (a).low) #define hret32(a) ((a).low) #define hget64(hi,lo,a) ((lo) = (a).low, (hi) = (a).high) #define hrethigh(a) ((a).high) #define hset32(a,i) ((a).high = 0, (a).low = (i)) #define hset64(a,hi,lo) ((a).high = (hi), (a).low = (lo)) #define hsethigh(a,hi) ((a).high = (hi)) #define hleftshift(a,amt) ((a).high <<= (amt), \ (a).high |= (((a).low) >> \ ((sizeof(unsigned int) * 8) - (amt))), \ (a).low <<= (amt)) #define hrightshift(a,amt) ((a).low >>= (amt), \ (a).low |= (((a).high) << \ ((sizeof(unsigned int) * 8) - (amt))), \ (a).high >>= (amt)) /* horinbits() macro simply does (a |= b) */ #define horinbits(a, b) (((a).low |= (b).low), \ ((a).high |= (b).high)) /* horinbits32() macro does (a |= b) for the lower 32 bits only */ #define horinbits32(a, b) ((a).low |= (b).low) /* * Like horinbits32, but "or in" a 32-bit int rather than the * low end of a hyper. */ #define horinbitsint(a, b) ((a).low |= (b)) /* * horinbitshighint() -- same as horinbitsint, but "or" into * high-order int */ #define horinbitshighint(a, b) ((a).high |= (b)) /* * handinbits() macro clears those bits in a which are cleared * (unset) in b (a &= b) */ #define handinbits(a, b) (((a).low &= ((b).low)), \ ((a).high &= ((b).high))) /* * hremovebits() macro clears those bits in a which are set * in b (a &= ~b) */ #define hremovebits(a, b) (((a).low &= ~((b).low)), \ Strange Page 9 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 ((a).high &= ~((b).high))) /* * hremovebits32() macro clears those bits in the lower 32-bits of a * which are set in b (a.low &= ~b). */ #define hremovebits32(a, b) ((a).low &= ~((b).low)) /* * hremovebitsint() macro -- Like hremovebits32, but use a 32-bit int * as the bit-removal mask instead of the low end of a hyper. */ #define hremovebitshighint(a, b) ((a).high &= ~(b)) /* hissubset() macro returns TRUE iff a is a subset of b */ #define hissubset(a, b) (((((a).low) & ~((b).low)) && \ (((a).high) & ~((b).high))) == 0) /* The algorithm here is to check for two cases that cause a carry. * If the top two bits are different then if the sum has the top bit * off then there must have been a carry. If the top bits are both * one then there is always a carry. We assume 32 bit longs and twos * complement arithmetic. */ #define SIGN 0x80000000 #define hadd32(a,i) \ (((((a).low ^ (int)(i)) & SIGN) \ ? (((((a).low + (int)(i)) & SIGN) == 0) && (a).high++) \ : (((a).low & (int)(i) & SIGN) && (a).high++)), \ (a).low += (int)(i)) #define hadd(a,b) (hadd32((a),(b).low), (a).high += (b).high) #define hsub(a,b) (hadd32((a), ~((b).low)), \ (a).high += ~((b).high), \ hadd32((a), 1)) #define hincr(a) if (!(++((a).low))) ((a).high)++ /* Decrement a hyper -- note: caller must check for a == 0!! */ #define hdecr(a) \ MACRO_BEGIN \ if((a).low > 0) (a).low--; \ else { \ (a).low = ~0; \ (a).high--; \ } \ MACRO_END #define hinit(high, low) { (high), (low) } Strange Page 10 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 /* hinitones -- used to initialize a hyper to all ones when declared. */ #define hinitones { ~(0), ~(0) } /* hinitzero -- used to initialize a hyper to zero when declared. */ #define hinitzero { 0, 0 } #else /* HAS_64BIT_SCALAR_TYPE */ /* ************************************************* * 64-bit versions of the hyper-handling macros. * ************************************************* */ #define MAX_OFFSET 0x7fffffffffffffff /* * Macros to convert longs to and from hypers */ #define htolong(l, h) ((l) = (h)) #define hfromlong(h, l) ((h) = (l)) #define hretlong(a) (a) #define horinlong(a, l) ((a) |= (l)) /* * usage of hcmp: * (a b) * can be expressed as: * (hcmp(a, b) 0) * where is one of: * < <= == > >= */ #define hcmp(a,b) ((a) < (b) ? -1 : ((a) > (b) ? 1 : 0)) /* as hcmp(), but compare a hyper to a long instead of hyper to hyper */ #define hcmplong(h, l) ((h) < (l) ? -1 : ((h) > (l) ? 1 : 0)) #define hsame(a,b) ((a) == (b)) #define hiszero(a) ((a) == 0L) #define hisones(a) ((a) == (~(0L))) #define hfitsin32(a) (((a) & 0xffffffffL) == 0L) #define hset(a,b) ((a) = (b)) #define hzero(a) ((a) = 0L) #define hones(a) ((a) = (~(0L))) #define hget32(i,a) ((i) = (unsigned32) (a) & 0xffffffffL) #define hret32(a) ((unsigned32) (a) & 0xffffffffL) #define hget64(hi,lo,a) ((lo) = ((unsigned32) (a) & 0xffffffffL), \ ((hi) = ((unsigned32) ((a) >> 32)))) #define hrethigh(a) ((unsigned32) ((a) >> 32)) #define hset32(a,i) ((a) = (unsigned long) (i)) #define hset64(a,hi,lo) ((a) = ((((unsigned long)(hi)) << 32) | \ Strange Page 11 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 (unsigned long)(lo))) #define hsethigh(a,hi) ((a) = ((unsigned long)(hi)) << 32 | \ (a) & 0xffffffffL) #define hleftshift(a,amt) ((a) <<= (amt)) #define hrightshift(a,amt) ((a) >>= (amt)) /* horinbits() macro simply does (a |= b) */ #define horinbits(a, b) ((a) |= (b)) /* horinbits32() macro does (a |= b) for the lower 32 bits only */ #define horinbits32(a, b) ((a) |= ((b) & 0xffffffffL)) /* * Like horinbits32, but "or in" a 32-bit int rather than the * low end of a hyper. */ #define horinbitsint(a, b) ((a) |= (b)) /* * horinbitshighint() -- same as horinbitsint, but "or" into * high-order int */ #define horinbitshighint(a, b) ((a) |= (((unsigned long)(b)) << 32)) /* * handinbits() macro clears those bits in "a" which are cleared * (unset) in "b" (a &= b) */ #define handinbits(a, b) ((a) &= (b)) /* * hremovebits() macro clears those bits in "a" which are set * in "b" (a &= ~b) */ #define hremovebits(a, b) ((a) &= ~(b)) /* * hremovebits32() macro clears those bits in the lower 32-bits of * "a" which are set in "b" (a.low &= ~b). */ #define hremovebits32(a, b) ((a) &= (~(b) & 0xffffffffL)) /* * hremovebitsint() macro -- Like hremovebits32, but use a 32-bit int * as the bit-removal mask instead of the low end of a hyper. */ #define hremovebitsint(a, b) ((a) &= (~(b) & 0xffffffffL)) /* * hremovebitshighint() -- same as hremovebitsint, but affects * high-order int of 'a' rather than low-order int. Strange Page 12 DCE-RFC 51.1 DFS 64-Bit Code Cleanup February 1994 */ #define hremovebitshighint(a, b) ((a) &= (~(b) << 32)) /* hissubset() macro returns TRUE iff "a" is a subset of "b" */ #define hissubset(a, b) (((a) & ~(b)) == 0) #define hadd32(a,i) ((a) += (i)) #define hadd(a,b) ((a) += (b)) #define hsub(a,b) ((a) -= (b)) #define hincr(a) ((a)++) /* * Decrement a hyper -- note: caller must check for a == 0!! */ #define hdecr(a) ((a)--) /* hinit -- used to initialize a hyper by providing hi & lo ints. */ #define hinit(high, low) { (((unsigned long)(high)) << 32) | \ ((unsigned long)(low)) } /* hinitones -- used to initialize a hyper to all ones when declared. */ #define hinitones { ~(0L) } /* hinitzero -- used to initialize a hyper to zero when declared. */ #define hinitzero { 0L } #endif /* HAS_64BIT_SCALAR_TYPE */ REFERENCES [RFC 51.0] D. Delgado, " DFS Interoperability Issues for 32-bit and 64-bit Architectures ", Nov 1993. AUTHOR'S ADDRESS Stephen H. Strange Internet email: strange@zk3.dec.com Digital Equipment Corporation Telephone: +1-603-881-0595 110 Spit Brook Rd. Nashua, NH 03062 USA Strange Page 13