Technotes


You Want Permission to do What?!!



Technote FL 37 Updated April 1999



Written by: Jim Luther September 1991 through September 1994

Updated by: Jim Luther April 1999


This note gives an in-depth explanation of the File Manager and AFP permission models used by the File Manager to open files. It also tells how a File Sharing or AppleShare file server implements the AFP permission model on the server Macintosh.


First there were the original four File Manager open permissions: whatever permission is currently allowed, read only permission, write only permission, and exclusive read/write permission. Shared read/write permission was added with the HFS version of the File Manager. AppleShare introduced AppleTalk Filing Protocol (AFP) deny-mode permissions and the translation of standard File Manager permissions to AFP deny-mode permissions. System 7 added one more piece to our permissions pie; local File Sharing and AppleShare permissions. There are also several foreign file systems which use other permission models. This note gives an explanation of each permission model and gives some code that may make your application more robust.
The original four File Manager permissions are simple to understand and simple to use. The four permissions available under this model are:

Table 1a-The Original Four Permissions

Using the original permission set, the File Manager gives only one access path to a file fork the permission to write. The File Manager decides whether permission to write will be granted based on what permissions other already open access paths may have and the current lock state of the file. The File Manager does not deny new read access paths to a file fork.

When the hierarchical version of the File Manager was introduced, shared read/write permission was added to the original four permissions. This permission allows multiple access paths to be opened for both reading and writing to a file fork.

Table 1b-Shared Read/Write Permission

As noted in Inside Macintosh: Files on page 2-8, "Shared read/write permission allows multiple access paths for writing and reading. It is safe to have multiple read/write paths open to a file only if there is some way of locking a portion of the file before writing to that portion of the file." The PBLockRange and PBUnlockRange functions provide the mechanism to lock a portion of a file. However, range locking is usually provided only by foreign file systems where shared file access is fully supported. See Inside Macintosh: Files, Inside AppleTalk, and Technical Note FL 26 - Lock, Unlock the Range, for more information on range locking and a method for determining whether PBLockRange and PBUnlockRange are supported on a particular file.

To summarize, with the original File Manager permission model you can always get an access path with read permission to a file. The File Manager will only deny write permission if the file is locked or if another access path to the file already has permission to write. Shared read/write permission allows multiple read and write access paths to a file, but should not be used unless range locking is available. Access to a file can be reduced to the following table.

Current File Access State

file is locked; other access paths
to file may or may not be open



file is not locked; no other access paths are open



file is not locked and all other open access paths
are read only access


file is not locked and another open access path has write access


file is not locked and other open access paths are read/write shared access


Permission Requested

fsCurPerm
fsRdPerm
fsWrPerm
fsRdWrPerm
fsRdWrShPerm

fsCurPerm
fsRdPerm
fsWrPerm
fsRdWrPerm
fsRdWrShPerm

fsCurPerm
fsRdPerm
fsWrPerm
fsRdWrPerm
fsRdWrShPerm

fsCurPerm
fsRdPerm
fsWrPerm
fsRdWrPerm
fsRdWrShPerm

fsCurPerm
fsRdPerm
fsWrPerm
fsRdWrPerm
fsRdWrShPerm

Permission Granted

read
read
none (permErr)
none (permErr)
none (permErr)

read/write
read
read/write
read/write
read/write/shared

read/write
read
read/write
read/write
read/write/shared

none (opWrErr)
read
none (opWrErr)
none (opWrErr)
none (opWrErr)

none (opWrErr)
read
none (opWrErr)
none (opWrErr)
read/write/shared


Table 2-File Manager Synchronization Rules

An open with write permission request (fsCurPerm, fsWrPerm, fsRdWrPerm, or fsRdWrShPerm) does not fail on a locked volume and the write bit in ioFCBFlags returned by PBGetFCBInfo will be set indicating that data can be written to the file. As noted in Inside Macintosh: Files on page 2-8, you won't discover this until you receive an error (either vLckdErr or wPrErr) on the first call that attempts to write to the file or change the file's logical or physical end-of-file. You can preflight for this condition by calling PBHGetVInfo and checking the hardware and software locked bits in ioVAtrb.

Warning: Even though the Open calls return the ioRefNum of an existing access path when the call fails with opWrErr, that ioRefNum should never be used unless your application owns that particular access path to the file. If another application or process owns that particular access path to the file, it could close it at any time and then if the File Control Block (FCB) is reused due to another file being opened, you could be accessing the wrong file! Even if you own the original access path, you should be very careful, because calls you make to the file could change the file's mark (which will be shared).

The permission model used by AppleTalk Filing Protocol (AFP) is designed to work in an environment where several different users could share access to a file concurrently. An application opening a file on an AFP file server or a file system that supports AFP's permission model can specify an access mode (read, write, read/write, or none) and a deny mode (deny-read, deny-write, deny-read/write, or deny-none). The synchronization rules using AFP's permission model can be summarized in the following table (borrowed from the File sharing modes section in chapter 13 of Inside AppleTalk). A dot indicates a new open call has succeeded; otherwise, it has failed.

Figure 1-AFP Synchronization Rules

Note that in addition to the synchronization rules listed above, an attempt to open a file on an AFP volume can fail if write access is requested and the file is "locked" (the AFP WriteInhibit file attribute is set for the file).

Volumes that support AFP deny-mode permissions and thus, the PBHOpenDeny and PBHOpenRFDeny functions, can be identified by checking the bHasOpenDeny bit returned in the vMAttrib field by PBHGetVolParms. You should use the PBHOpenDeny and PBHOpenRFDeny functions if you really want to ensure the access permission and deny-mode permission you request is what you get. PBHOpenDeny and PBHOpenRFDeny are not retried in any way. If the file cannot be opened because of a deny conflict, the error afpDenyConflict is returned and ioRefNum is set to zero.

If you don't want to special case volumes that support AFP deny-mode permissions, you can use the File Manager permissions described in the previous section of this Note. The next section of this Note describes how File Manager permissions are translated to AFP deny-mode permissions.

AppleShare (and other AFP file servers) use the AppleTalk Filing Protocol (AFP) deny-mode permissions exclusively. So that applications using classic File Manager permissions will work, the foreign file system used by AppleShare (on each workstation) translates classic File Manager permissions into the AFP deny-mode permissions.

To keep applications from damaging each other's files, the basic rule of file access (in translating permissions for AppleShare volumes) was changed to "single writer OR multiple readers, but not both." Because of this change, two applications cannot both have access to the same file unless both are read only; this eliminates the danger of reading from a file when it is inconsistent.

Note: This change in the basic rule currently applies only to AppleShare volumes. Should a future version of the File Manager incorporate this change for local volumes, then an application expecting to get more than one path to a file (with at least one read/write) will fail.

The AppleShare foreign file system is used to access volumes on AppleShare and other AFP file servers. Files opened by a workstation must access the file on the AppleShare volume through an AFP access path maintained by the foreign file system. In some situations (as you'll see later), the AFP access path from a single workstation to the server may be shared by two or more open File Manager access paths on that workstation. In those cases, the File Manager will only allow one of the File Manager access paths write permission. A File Manager access path is an access path between an application and a file on either a local HFS volume or on a volume accessed by an foreign file system.

The following table shows how the classic permissions described in the File Manager are translated into the AFP deny-mode permissions.

Table 3-Translation of File Manager Permissions to AFP deny-mode Permissions on a Remote Volume

fsRdPerm acts as you would expect: browsing access is achieved if there is no existing write access path to the file.

For fsCurPerm, you also get what you'd expect: "whatever is available" has always meant "read/write if you can, otherwise, read only". The deny portions of the translation are important for enforcing the updated basic rule of file access: if there's an existing read or write access path to a file being opened with fsCurPerm, the first set of permissions will fail; the second set, browsing access, will then succeed only if there is no existing write access path to the file.

fsRdWrPerm is also retried as read-only, to simulate the case where a file is being opened from a locked disk. Elsewhere, it's pointed out that fsRdWrPerm is granted even if the volume is locked, and that an error won't be returned until a PBWrite (or PBSetEOF or PBAllocate) call is made. The same is now true for a read-only folder on an AppleShare volume.

Note: Changing access privileges of a folder does not change the access established for an open path to a file in that folder. This is unlike the case where you eject a disk, remove the hardware lock, and can then write to an open file on it.

When the System 7 File Sharing or AppleShare file server is on, shared volumes on your Macintosh can be accessed by both the local user and remote users. So, what permission model is used? File Manager or AFP deny-mode permissions? The answer is both!

To a remote user, your system looks like any other AFP file server on the network. When a remote user opens a file on your system, they are always opening the file using AFP deny-mode permissions. However, the local user is running in a hybrid environment and can use either the File Manager permission model or the AFP deny-mode permission model.

When a local user opens a file using the File Manager permission model, everything works just like it does when file sharing is off, unless the file is already open using deny-mode permissions. Deny-mode permissions take precedence over File Manager permissions.

When File Sharing is on and a new local open call is made using File Manager permissions, the call is made and deny-mode permissions are added for synchronizing with remote users. If no remote users have the file open, then the call acts just as it would without File Sharing. If a remote user has the file open, then the deny-mode permissions are used. (Note: afpAccessDenied is returned instead of permErr when a file is locked and File Sharing is on.) Here's the translation used when a remote user has the file open and deny-permissions must be respected:

Table 4-Translation of File Manager Permissions to

AFP deny-mode Permissions on a Local Shared Volume

when a Remote User has the File Open

One task the File Sharing extension performs when a user turns File Sharing on is assign deny-mode permissions to all open files. The following table shows how the deny-mode permissions are assigned to open files.

Table 5-File Manager to Deny-mode Permission Translation

What happens when a user turns File Sharing off? Things revert to the File Manager permissions. However, there's the slight problem of translating the sixteen AFP permissions to the five File Manager permissions. Here's another table that shows what happens when you turn File Sharing off.


Table 6-Deny-mode to File Manager Permission Translation

Foreign file systems that access non-Macintosh systems probably do not use the File Manager or AFP permission models on the host system. However, those foreign file systems must still map their permission model to one of the two permission models supported by the File Manager. An application should use the PBGetForeignPrivs and PBSetForeignPrivs functions provided by the System 7 File Manager if it needs to directly manipulate the permissions of a non-Macintosh permission model. See Inside Macintosh: Files and contact the publisher of the foreign file system for more information.

That's a good question (at least I thought it was when I asked myself). You know what permissions you'll get if the volume the file is on supports AFP deny-mode permissions and you used PBHOpenDeny or PBHOpenRFDeny. The chances of getting what you ask for are somewhat slimmer if you use File Manager permissions to open a file. There two ways to deal with this problem.

The first method is to try with the permissions you want and if that fails, keep retrying asking for fewer permissions until you succeed (or decide to give up). The idea here is you know what permissions you want before you open a file and if you don't get it, you get to decide what to try next. The OpenAware routines in the Developer Technical Support MoreFiles sample code can be used for that purpose because it attempts to give you the AFP deny-mode permissions you request and if it cannot, it tries to give you the equivalent File Manager permissions.

The OpenAware routines let you deal with one permission model, the more complete deny-mode permission model. The following constants can be used to specify deny-mode permissions.

/*
**  Deny mode permissions for use with the HOpenAware, HOpenRFAware,
**  FSpOpenAware, and FSpOpenRFAware functions.
*/

enum
{
    dmNone            = 0x0000,
    dmNoneDenyRd      = 0x0010,
    dmNoneDenyWr      = 0x0020,
    dmNoneDenyRdWr    = 0x0030,
    dmRd              = 0x0001,  /* Single writer, multiple readers; the readers
*/
    dmRdDenyRd        = 0x0011,
    dmRdDenyWr        = 0x0021,  /* Browsing - equivalent to fsRdPerm */
    dmRdDenyRdWr      = 0x0031,
    dmWr              = 0x0002,
    dmWrDenyRd        = 0x0012,
    dmWrDenyWr        = 0x0022,
    dmWrDenyRdWr      = 0x0032,
    dmRdWr            = 0x0003,  /* Shared access - equivalent to fsRdWrShPerm */
    dmRdWrDenyRd      = 0x0013,
    dmRdWrDenyWr      = 0x0023,  /* Single writer, multiple readers; the writer
*/
    dmRdWrDenyRdWr    = 0x0033   /* Exclusive access - equivalent to fsRdWrPerm
*/
};

Here is a self-contained version of the HOpenAware function (it makes no calls to other functions in the MoreFiles library).

/*
**  A self-contained version of HOpenAware. See MoreFiles for the real thing.
*/

pascal  OSErr  HOpenAware(short vRefNum,
                          long dirID,
                          ConstStr255Param fileName,
                          short denyModes,
                          short *refNum)
{
    HParamBlockRec pb;
    OSErr result;
    GetVolParmsInfoBuffer volParmsInfo;
    
    *refNum = 0;  /* default */
    
    /* Get volume attributes */
    /* This preflighting is needed because Foreign File Access based file systems
*/
    /* don't return the correct error result to the OpenDeny call */
    
    pb.ioParam.ioNamePtr = (StringPtr)fileName;  /* might be a full pathname */
    pb.ioParam.ioVRefNum = vRefNum;
    pb.ioParam.ioBuffer = (Ptr)&volParmsInfo;
    pb.ioParam.ioReqCount = sizeof(GetVolParmsInfoBuffer);
    result = PBHGetVolParmsSync(&pb);
    if ( (result == noErr) || (result == paramErr) )
    {
        /* paramErr is OK, it just means this volume doesn't support GetVolParms
*/
        
        if ( (result == noErr) &&
             ((volParmsInfo.vMAttrib & (1L << bHasOpenDeny)) != 0) )
        {
            /* OpenDeny is supported, so use it */
            
            pb.ioParam.ioMisc = NULL;
            pb.fileParam.ioFVersNum = 0;
            pb.fileParam.ioNamePtr = (StringPtr)fileName;
            pb.fileParam.ioVRefNum = vRefNum;
            pb.fileParam.ioDirID = dirID;
            pb.accessParam.ioDenyModes = denyModes;
            result = PBHOpenDenySync(&pb);
            *refNum = pb.ioParam.ioRefNum;
        }
        else
        {
            /* OpenDeny isn't supported, so try File Manager Open functions */
            
            result = noErr; /* result back to noErr */
            
            if ( (denyModes & dmWr) != 0 )
            {
                /* If request includes write permission, then see if the volume
*/
                /* is locked by hardware or software. The HFS file system doesn't
*/
                /* check for this when a file is opened - you only find out later
*/
                /* when you try to write and the write fails with a wPrErr */
                /* or a vLckdErr. */
                
                Str255 tempPathname;
                
                pb.volumeParam.ioVRefNum = vRefNum;
                /* Make a copy of the fileName and */
                /* use the copy so fileName isn't trashed */
                BlockMoveData(fileName, tempPathname, fileName[0] + 1);
                pb.volumeParam.ioNamePtr = (StringPtr)tempPathname;
                pb.volumeParam.ioVolIndex = -1;  /* use ioNamePtr/ioVRefNum */
                result = PBHGetVInfoSync(&pb);
                
                if ( result == noErr )
                {
                    if ( (pb.volumeParam.ioVAtrb & 0x0080) != 0 )
                    {
                        result = wPrErr;  /* volume locked by hardware */
                    }
                    else if ( (pb.volumeParam.ioVAtrb & 0x8000) != 0 )
                    {
                        result = vLckdErr;  /* volume locked by software */
                    }
                }
            }
            
            if ( result == noErr )  /* are we still OK? */
            {
                pb.ioParam.ioMisc = NULL;
                pb.fileParam.ioFVersNum = 0;
                pb.fileParam.ioNamePtr = (StringPtr)fileName;
                pb.fileParam.ioVRefNum = vRefNum;
                pb.fileParam.ioDirID = dirID;
                
                /* Set File Manager permissions to closest thing possible */
                pb.ioParam.ioPermssn = ( (denyModes == dmWr) ||
                                         (denyModes == dmRdWr)) ?
                                       (fsRdWrShPerm) :
                                       (denyModes % 4);
                
                result = PBHOpenDFSync(&pb);    /* Try OpenDF */
                if ( result == paramErr )
                    result = PBHOpenSync(&pb);  /* OpenDF not supported, try
Open */
                *refNum = pb.ioParam.ioRefNum;
            }
        }
    }
    
    return ( result );
}

Another way to know what permissions you have is to open the file and then check to see what permissions you actually received (some people find it's easier to ask for forgiveness than to ask for permission). You can use the following routine to see what File Manager permissions you received after you've opened a file.

/*
**  This function returns the File Manager permissions of an open file
**  specified by refNum. Any errors are returned in the function result.
**  If the result is noErr, then permission will contain fsRdPerm, fsRdWrPerm,
**  or fsRdWrShPerm.
*/
pascal  OSErr  GetPermission(short refNum,
                             short *permission)
{
    OSErr                  result;
    FCBPBRec               fcbPB;
    HParamBlockRec         pb;
    GetVolParmsInfoBuffer  buffer;
    
    enum
    {
        fcbFlgWMask        = 0x0100,     /* write permissions bit in FCBFlags */
        fcbFlgSMask        = 0x1000,     /* shared-write bit in FCBFlags */
        vcbWrProtMask      = 0x8080,     /* hardware and software locked bits */
                                         /* in vcbAtrb */
        userWriteACAccess  = 0x04000000  /* user has write access to directory */
    };
    
    /* Get the access path info from the FCB */
    fcbPB.ioNamePtr = NULL;
    fcbPB.ioVRefNum = 0;
    fcbPB.ioRefNum = refNum;  /* check this access path */
    fcbPB.ioFCBIndx = 0;
    result = PBGetFCBInfoSync(&fcbPB);
    if ( result == noErr )
    {
        /* Now, look at ioFCBFlags to see what the File Manager thinks */
        /* it can do with this file */
        
        if ( (fcbPB.ioFCBFlags & fcbFlgSMask) != 0 )
        {
            /* shared bit is set in the FCB */
            *permission = fsRdWrShPerm;  /* shared bit is set in the FCB */
        }
        else if ( (fcbPB.ioFCBFlags & fcbFlgWMask) != 0 )
        {
            /* Write bit is set in the FCB, but a locked volume or */
            /* a read-only folder could squelch that idea. */
            
            /* First, see if the volume supports AFP access control. */
            pb.ioParam.ioNamePtr = NULL;
            pb.ioParam.ioVRefNum = fcbPB.ioFCBVRefNum;
            pb.ioParam.ioBuffer = (Ptr)&buffer;
            pb.ioParam.ioReqCount = sizeof(buffer);
            result = PBHGetVolParmsSync(&pb);
            if ( (result == noErr) && 
                 ((buffer.vMAttrib & (1L << bAccessCntl)) != 0) )
            {
                /* Use GetDirAccess to see if we can really write */
                pb.accessParam.ioNamePtr = NULL;
                pb.accessParam.ioVRefNum = fcbPB.ioFCBVRefNum;
                pb.fileParam.ioDirID = fcbPB.ioFCBParID;
                result = PBHGetDirAccessSync(&pb);
                if ( result == noErr )
                {
                    if ( (pb.accessParam.ioACAccess & userWriteACAccess) != 0
)
                    {
                        /* this user has folder write access */
                        *permission = fsRdWrPerm;
                    }
                    else
                    {
                        /* this user hasn't folder write access */
                        *permission = fsRdPerm;
                    }
                }
            }
            else
            {
                /* GetVolParms isn't supported or */
                /* the volume doesn't support AFP access control */
                
                /* Check for locked volume that will prevent writes */
                pb.volumeParam.ioNamePtr = NULL;
                pb.volumeParam.ioVRefNum = fcbPB.ioFCBVRefNum;
                pb.volumeParam.ioVolIndex = 0;    /* use ioVRefNum only */
                result = PBHGetVInfoSync(&pb);
                if ( result == noErr )
                {
                    if ( (pb.volumeParam.ioVAtrb & vcbWrProtMask) != 0 )
                    {
                        /* locked volume, it can't really write */
                        *permission = fsRdPerm;
                    }
                    else
                    {
                        /* real write access */
                        *permission = fsRdWrPerm;
                    }
                }
            }
        }
        else
        {
            /* write bit wasn't set in FCB */
            *permission = fsRdPerm;
        }
    }
    
    return ( result );
}

By understanding the information provided in this Technical Note and the routines in the Apple Developer Support sample code MoreFiles, you should be able to get the access you require when you open a file.

Change History


Further Reference:


Technotes
Previous Technote | Contents | Next Technote