The Amazing GetCatInfo

(Note: Most of the following information (except the FutureBasic implementations) can be found in Inside Macintosh: Files and in the Finder Interface chapter of Inside Macintosh: Macintosh Toolbox Essentials.)


Contents

Introduction
Calling Convention
Setting up the Parameter Block's Contents
How GetCatInfo Determines the File or Folder
A Few Examples
More about the Parameter Block
A Complete Description of All the Parameters


Introduction
If you want to find out virtually anything about a directory or a file on any volume currently mounted on your Mac (including remote volumes on a network that are "mounted" on your Desktop), then FN GETCATINFO is probably the best way to do it. This Toolbox function, which is also known as "PBGetCatInfo," returns a ton of information which is contained in a parameter block, a chunk of memory that's up to 108 bytes long. It's similar to another Toolbox routine called "PBHGetFInfo" (which can be called in FutureBasic by using the FN HGETFILEINFO function or the GET FILE INFO statement). However, I usually prefer to use FN GETCATINFO because it's more versatile, and it returns all the information that "PBHGetFInfo" returns, and more.

Calling Convention
Before you call FN GETCATINFO from a FutureBasic program, you need to set aside some area of memory that's at least 108 bytes long. This area of memory will comprise the "parameter block"; you need to put certain information into the parameter block before you call FN GETCATINFO. The parameter block is also the place where FN GETCATINFO puts all of the information that it returns. If pbPtr& is a pointer to the parameter block, then you can call FN GETCATINFO like this:

OSErr% = FN GETCATINFO(pbPtr&)
If you want, you can define the parameter block as a 108-byte FutureBasic variable, like this:
DIM pb.ioHFQElSiz   '(_ioHFQElSiz = 108)
Then you can call FN GETCATINFO like this:
OSErr% = FN GETCATINFO(@pb)
If the call to FN GETCATINFO was successful, then the result code _noErr (zero) will be returned in OSErr%. Result codes that indicate failure include _fnfErr (file not found) and _nsvErr (no such volume), and others.

You can also call FN GETCATINFO asynchronously. See the ioCompletion parameter for more information.

Setting up the parameter block's contents
There are four or five parameters in the parameter block that you need to set correctly before you call FN GETCATINFO. One of the parameters tells FN GETCATINFO where your "completion routine" is. You don't need to concern yourself with completion routines unless you intend to call FN GETCATINFO asynchronously. If you intend to do that, then you need to set the ioCompletion& parameter either to the address of your completion routine, or to zero (if you set it to zero, you're indicating that you don't have a completion routine. I like to use the FutureBasic constant "_nil" for this.) In FutureBasic, you can use statements like the following to set the ioCompletion& parameter:

pb.ioCompletion& = proCAddress&
- or -
pbPtr&.ioCompletion& = procAddress&
If you call FN GETCATINFO synchronously (which is the usual way to call it), then the ioCompletion& parameter is ignored, and you don't have to set it. See the description of the ioCompletion& parameter for more information about calling FN GETCATINFO asynchronously.

The purpose of other four input parameters is to tell FN GETCATINFO exactly which file or folder you are interested in. These parameters are:

  pb.ioNamePtr&
  pb.ioVRefNum%
  pb.ioFDirIndex%
  pb.ioDirID&
(You could alternatively use the "pbPtr&" notation with these; e.g., "pbPtr&.ioNamePtr&," etc.)

The way these parameters are used is very versatile, but at the same time somewhat complex, and deserves a thorough discussion, which we present below. It's worth noting that the description in Inside Macintosh is incorrect regarding the interaction of these four parameters. The real scoop is given here.

How GETCATINFO determines the file or folder
First, let's define something called the "base directory." (Don't look for this term in Inside Macintosh; I just made it up, for the purpose of this discussion.) The "base directory" is a directory that you specify by means of the ioVRefNum% parameter and the ioDirID& parameter, according to rules I'll explain in a minute. When you call FN GETCATINFO, you'll be getting information about one of the following:

The base directory is determined from the values you put into ioVRefNum% and ioDirID&, according to these rules: Okay, that's a lot to swallow, and we're not done yet. Take a cleansing breath...exhale...good.

Once the base directory is determined as above, then the ioNamePtr& and ioFDirIndex% parameters complete the determination of the object. This works as follows:

A word of caution: when ioFDirIndex% is nonzero, then the string pointed to by ioNamePtr& is ignored, but even in that case you still are required to set the value of ioNamePtr& properly before calling FN GETCATINFO. Specifically, you must either set it to _nil, or set it to the address of an area of memory which can receive a name. If you set it to _nil, then FN GETCATINFO won't return any name. If you set it to the address of a string buffer, then FN GETCATINFO will return the name of the item it finds into that buffer. If you fail to explicitly set ioNamePtr&, and it happens to contain some random nonzero number, then FN GETCATINFO will interpret that number as an address and will try to stuff the item's name into the memory at that address. In the worst case, this could crash your computer.

So remember:

It never does both.

A Few Examples
Because the above rules are pretty complex, a few examples are in order here.

Example 1. Return information about the root directory of the volume whose reference number is -1 (this is usually the startup volume). Note that the name of the root directory is just the volume's name.

DIM pb.108, 31 volName$
pb.ioNamePtr& = @volName$   	'Return volume name here.
pb.ioVRefNum% = -1		'volume reference number.
pb.ioFDirIndex% = -1		'Return info about dir. itself.
pb.ioDirID& = 0			'Use ioVRefNum% field alone for dir.
OSErr% = FN GETCATINFO(@pb)
Example 2. Return information about the item called "MyStuff" which is in the working directory whose reference number is -32544. (Note: in "real life," you would virtually never use a literal constant to represent a working directory reference number.)
DIM pb.108, 31 itemName$
itemName$ = "MyStuff"
pb.ioNamePtr& = @itemName$
pb.ioVRefNum% = -32544		'working directory ref. num.
pb.ioFDirIndex% = 0		'Refer to ioNamePtr& for item.
pb.ioDirID& = 0			'Use ioVRefNum% field alone for dir.
OSErr% = FN GETCATINFO(@pb)
Example 3. Return information about the current default directory.
DIM pb.108, 31 itemName$
pb.ioNamePtr& = @itemName$
pb.ioVRefNum% = 0	'Default volume.
pb.ioFDirIndex% = -1	'Return info about dir. itself.
pb.ioDirID& = 0		'0 = default dir, when ioVRefNum is also 0.
OSErr% = FN GETCATINFO(@pb)
Example 4. Return information about the root directory of the current default volume. Here we make use of the fact that every volume's root directory has a directory ID of "2."
DIM pb.108, 31 itemName$
pb.ioNamePtr& = @itemName$
pb.ioVRefNum% = 0		'Default volume.
pb.ioFDirIndex% = -1		'Return info about dir. itself.
pb.ioDirID& = 2			'2 always means "root directory".
OSErr% = FN GETCATINFO(@pb)
Example 5. Return information about the 6th item in the root directory of the volume currently in the floppy drive (the drive number of the floppy drive is 1).
DIM pb.108, 31 itemName$
pb.ioNamePtr& = @itemName$
pb.ioVRefNum% = 1		'Drive number.
pb.ioFDirIndex% = 6		'Return info about 6th item.
pb.ioDirID& = 0			'Use ioVRefNum% field alone for dir
OSErr% = FN GETCATINFO(@pb)
Example 6. Return information about the directory whose ID is 105, in the volume whose reference number is -2. Disregard the directory's name. (Note: in "real life," you would probably not use literal constants to represent a volume reference number and a directory ID, except in special cases.)
DIM pb.108
pb.ioNamePtr& = _nil		'Don't return the item's name.
pb.ioVRefNum% = -2		'Volume reference number.
pb.ioFDirIndex% = -1		'Return info about dir. itself.
pb.ioDirID& = 105		'Directory ID #.
OSErr% = FN GETCATINFO(@pb)
Example 7. Return information about the root directory of the volume called "MacintoshHD" (hopefully, there is only one volume with that name currently mounted).
DIM pb.108, 31 itemName$
itemName$ = "MacintoshHD:"	'non-leading colon makes it a full path name.
pb.ioNamePtr& = @itemName$
pb.ioVRefNum% = 0		'Ignored when full path used.
pb.ioFDirIndex% = 0		'Refer to ioNamePtr& for item.
pb.ioDirID& = 0			'Ignored when full path used.
OSErr% = FN GETCATINFO(@pb)
There are many more possible variations on these examples, but these should give you a taste of what you can do.

More about the parameter block
When you call FN GETCATINFO, the parameter block returns slightly different information depending on whether the selected item is a file or a directory. Sometimes you'll know in advance whether the item is a file or a directory, but other times you may not. After you call FN GETCATINFO, you can always determine whether the returned item is a file or directory by looking at bit 4 of the ioFlAttrib parameter that FN GETCATINFO returns. One way to do this is as follows:

itsADirectory = (PEEK(@pb.ioFlAttrib) AND BIT(4)) <> 0
- or -
itsADirectory = (PEEK(pbPtr& + _ioFlAttrib) AND BIT(4)) <> 0
In the above statements, itsADirectory will be set to _zTrue if the returned item is a directory, or to _false if the item is a file.

The input and output parameters for files and directories are summarized below. A right-pointing arrow () indicates an input parameter, a left-pointing arrow () indicates an output parameter, and a two-headed arrow () indicates a parameter which requires input and also provides output. The first 12 bytes in the block (which precede the ioCompletion& field) are used internally.

DirectionByte offsetParameters for filesParameters for directoriesParameter size
12 ioCompletion ioCompletion long integer
16 ioResult ioResult integer
18 ioNamePtr ioNamePtr long integer
22 ioVRefNum ioVRefNum integer
24 ioFRefNum   integer
28 ioFDirIndex ioFDirIndex integer
30 ioFlAttrib ioFlAttrib byte
31   ioAcUser byte
32 ioFlFndrInfo ioDrUsrWds 16 bytes
48 ioDirID / File ID ioDirID / ioDrDirID long integer
52 ioFlStBlk ioDrNmFls integer
54 ioFlLgLen   long integer
58 ioFlPyLen   long integer
62 ioFlRStBlk   integer
64 ioFlRLgLen   long integer
68 ioFlRPyLen   long integer
72 ioFlCrDat ioDrCrDat long integer
76 ioFlMdDat ioDrMdDat long integer
80 ioFlBkDat ioDrBkDat long integer
84 ioFlXFndrInfo ioDrFndrInfo 16 bytes
100 ioFlParID ioDrParID long integer
104 ioFlClpSiz   long integer

A Complete Description of all the Parameters

ioCompletion

You'll usually want to set this parameter to _nil (zero). If it's not _nil, then it should be set to the address of a "completion routine," which is a routine that you want FN GETCATINFO to automatically call after it's finished getting its info about the selected item. This is really only useful in cases where you want to call FN GETCATINFO "asynchronously." Calling asynchronously means that FN GETCATINFO puts the request for information onto a queue, and then returns immediately to your application, possibly before the information has actually been retrieved. The system then processes the GETCATINFO request in the background (as soon as it can get around to it), and meanwhile your application is free to do other stuff. When the system completes the GETCATINFO request, it calls your completion routine (if you specified one). You can use the completion routine as a way to notify your application that the GETCATINFO request has been processed. (Another way to check whether the request has been processed is to periodically check the value of the ioResult parameter, as explained below.) To call FN GETCATINFO asynchronously, call it like this:
OSErr% = FN GETCATINFO _async(@pb)
- or -
OSErr% = FN GETCATINFO _async(pbPtr&)
Calling FN GETCATINFO asynchronously can be useful if you're making a great number of calls, and/or you expect that it might take a while for the information to be returned (as may be the case when you're retrieving information about items on a remote network volume). If you do call FN GETCATINFO asynchronously, you must not dispose of the parameter block until the the routine completes (you can check ioResult periodically to determine when that happens).

ioResult

If you're making a synchronous call (as normally you'd do), then ioResult returns the same value that OSErr% (the function result) returns. But if you're making an asynchronous call, then OSErr% always returns _noErr. In this case, one way to determine whether the GETCATINFO request has been processed is to periodically check the value of ioResult. As long as ioResult contains a positive value, the request has not yet been processed. Eventually the system will put a result code (either _noErr (zero), or some negative error code) into ioResult. When this happens, you'll know that the GETCATINFO request has been processed.

ioNamePtr

The way this parameter is used depends on the value of the ioFDirIndex parameter.

ioVRefNum

This parameter helps (along with ioDirID) to determine the "base directory," as described in a previous section. FN GETCATINFO can interpret ioVRefNum in any of four distinct ways, depending on its value:

If ioVRefNum is:...it's interpreted as:
zerothe default volume
positivea drive ID number
in the range [-1..-16384] (?)a volume reference number
in the range [-16385..-32768] (?)a working directory reference number

 

ioFRefNum (files only)

If any running process has the file currently open, then ioFRefNum returns the reference number of one of the file's i/o access paths (there may be more than one access path active; for example if two processes are both reading the file concurrently). If no process has the file open, then ioFRefNum returns zero.

ioFDirIndex

This is one of the four parameters you use to specify which file or folder you want to get information about. If it's positive, then it's interpreted as an index number which indexes the items in the "base directory" . If it's zero or negative, it has other interpretations. See the section, "How GETCATINFO determines the file or folder" for more information.

ioFlAttrib

The bits in this one-byte field are either called the "file attributes" or the "directory attributes," depending on whether the returned item is a file or a directory.

The bits in the "file attributes" have these meanings:
BitMeaning
0Set if the file is locked
1Reserved
2Set if resource fork is open
3Set if data fork is open
4Always "0" for files
5-6Reserved
7Set if file (either fork) is open

The bits in the "directory attributes" have these meanings:
BitMeaning
0Set if the directory is locked*
1Reserved
2Set if the directory is within a shared area of the directory hierarchy**
3Set if the directory is a share point that is mounted by some remote user**
4Always "1" for directories
5Set if the directory is a share point**
6-7Reserved

*Does anybody know what this means? It's not the same as the "name locked" bit. Inside Macintosh says that this bit can be set or reset by the PBHSetFLock and PBHRstFLock functions, respectively, if the volume supports directory locking. I have so far been unable to set or reset it, nor determine what it means.

**A "share point" is a directory (or volume) on the local computer that you've enabled for sharing. A directory is "within a shared area of the directory hierarchy" if it's a share point or it's somewhere inside a share point. These three bits (2, 3 and 5) will be zero (for all directories) if file sharing is not currently turned on.

Probably the most popular bit in ioFlAttrib is bit 4, which determines whether the item is a file or a directory (and hence also determines how the remaining flags should be interpreted). In FutureBasic, an easy way to test whether a particular bit is set is by using the AND operator and the BIT function, as in this example:

OSErr% = FN GETCATINFO(@pb)
attrib = PEEK(@pb.ioFlAttrib)
itsADirectory = ((attrib AND BIT(4)) <> 0)
LONG IF itsADirectory
  itsLocked = ((attrib AND BIT(1)) <> 0)
  inSharedArea = ((attrib AND BIT(2)) <> 0)
  mountedSharePoint = ((attrib AND BIT(3)) <> 0)
  itsASharePoint = ((attrib AND BIT(5)) <> 0)
XELSE
  itsLocked = ((attrib AND BIT(1)) <> 0)
  resForkOpen = ((attrib AND BIT(2)) <> 0)
  dataForkOpen = ((attrib AND BIT(3)) <> 0)
  someForkOpen = ((attrib AND BIT(7)) <> 0)
END IF

ioAcUser (directories only)

The flags in this one-byte field provide information about the current user's access priveleges for the given directory.

BitMeaning
0Set if user does not have "See Folders" privelege
1Set if user does not have "See Files" privelege
2Set if user does not have "Make Changes" privelege
3-6Reserved
7Set if user is not an owner of the directory

Obviously, these flags are most meaningful when you're retrieving information about a directory that's on a remote server. If you retrieve information about a local directory, and you have sharing currently turned off, then FN GETCATINFO seems always to set all these flags to zeros (meaning, essentially, that you're the owner and you have full priveleges). Inside Macintosh has this to say:

"The PBGetCatInfo function returns the directory access rights in the ioACUser field only for shared volumes. As a result, you should set this field to 0 before calling PBGetCatInfo."

However, my experiments seem to indicate that a value is output to ioAcUser even when the volume in question is not shared. Go figure.

In FutureBasic, an easy way to test whether a particular bit is set is by using the AND operator and the BIT function, as in this example:

OSErr% = FN GETCATINFO(@pb)
ioAcUser = PEEK(@pb + 31)
'test whether bit "n" of ioAcUser is set:
bitIsSet = ((ioAcUser AND BIT(n)) <> 0)

ioFlFndrInfo (files only)

ioFlFndrInfo is a record of type "FInfo," and contains 16 bytes of "Finder Information" about the file. The fields of the FInfo record are described below.

ioDrUsrWds (directories only)

ioDrUsrWds is a record of type "DInfo," and contains 16 bytes of "Finder Information" about the folder. The fields of the DInfo record are described below.

ioDirID

On input, this parameter is used (along with ioVRefNum) to determine the "base directory," as described in a previous section. Because FN GETCATINFO changes the contents of these bytes on output, you must be careful to reset this parameter to the correct value before you use this same parameter block again.

File ID (files only)

On output, this parameter contains the "File ID" number for the file. Every file on a given volume has a unique File ID number which is assigned to it by the MacOS when the file is created, and which doesn't change even if the file is renamed or moved to a different folder. It's analogous to the "Directory ID" number that directories have. The Alias Manager uses File ID numbers internally, to help keep track of a file by means of its alias. Since you can't directly search for files based on their File ID number, your program probably won't have much use for this parameter.

ioDrDirID (directories only)

On output, this parameter contains the Directory ID number for the directory. Depending on how you set up the input parameters, this value may or may not be the same number that you put into ioDirID when you called FN GETCATINFO. Note that the directory ID of a volume's root directory is always "2."

ioFlStBlk (files only)

Inside Macintosh claims that this parameter contains the block number of the first allocation block of the file's data fork, and that this value will be zero if the file's data fork is empty. However, I think this is unreliable; In my tests, this parameter always returns zero, for all files.

ioDrNmFls (directories only)

The number of items (files and directories) within the directory's first level.

ioFlLgLen (files only)

This returns the "logical length" of the file's data fork (i.e., the number of bytes of data in the fork). This is the same as the number returned by FutureBasic's LOF(fileID, 1) function.

ioFlPyLen (files only)

This returns the "physical length" of the file's data fork. This is the number of bytes that the data fork actually occupies on disk; it's always a multiple of the disk's "allocation block size," and it's always greater than or equal to the ioFlLgLen value. If ioFlPyLen is zero, then the file doesn't have a data fork. To determine the total amount of disk space used by the file, add ioFlPyLen to ioFlRPyLen.

ioFlRStBlk (files only)

Inside Macintosh claims that this parameter contains the block number of the first allocation block of the file's resource fork. However, I think this is unreliable; In my tests, this parameter always returns zero, for all files.

ioFlRLgLen (files only)

This returns the "logical length" of the file's resource fork (i.e., the number of bytes of data in the fork).

ioFlRPyLen (files only)

This returns the "physical length" of the file's resource fork. This is the number of bytes that the resource fork actually occupies on disk; it's always a multiple of the disk's "allocation block size," and it's always greater than or equal to the ioFlRLgLen value. If ioFlRPyLen is zero, then the file doesn't have a resource fork. To determine the total amount of disk space used by the file, add ioFlPyLen to ioFlRPyLen.

ioFlCrDat (files only)

The date and time of the file's creation, specified in seconds since midnight, January 1, 1904. You can pass this value to the SecondsToDate function (in FutureBasic, CALL SECS2DATE), to get the individual elements of the date and time.

ioDrCrDat (directories only)

The date and time of the directory's creation, specified in seconds since midnight, January 1, 1904. You can pass this value to the SecondsToDate function (in FutureBasic, CALL SECS2DATE), to get the individual elements of the date and time.

ioFlMdDat (files only)

The date and time of the latest modification to the file, specified in seconds since midnight, January 1, 1904. You can pass this value to the SecondsToDate function (in FutureBasic, CALL SECS2DATE), to get the individual elements of the date and time.

ioDrMdDat (directories only)

The date and time of the latest modification to the directory, specified in seconds since midnight, January 1, 1904. You can pass this value to the SecondsToDate function (in FutureBasic, CALL SECS2DATE), to get the individual elements of the date and time.

ioFlBkDat (files only)

The date and time of the last backup of the file, specified in seconds since midnight, January 1, 1904. You can pass this value to the SecondsToDate function (in FutureBasic, CALL SECS2DATE), to get the individual elements of the date and time.

ioDrBkDat (directories only)

The date and time of the last backup of the directory, specified in seconds since midnight, January 1, 1904. You can pass this value to the SecondsToDate function (in FutureBasic, CALL SECS2DATE), to get the individual elements of the date and time.

ioFlXFndrInfo (files only)

ioFlXFndrInfo is a record of type "FXInfo," and contains 16 bytes of "Extended Finder Information" about the file. The fields of the FXInfo record are described below.

ioDrFndrInfo (directories only)

ioDrFndrInfo is a record of type "DXInfo," and contains 16 bytes of "Extended Finder Information" about the directory. The fields of the DXInfo record are described below.

ioFlParID (files only)

The directory ID of the file's parent directory.

ioDrParID (directories only)

The directory ID of the specified directory's parent. Note that if you're getting information about some volume's root directory, that directory doesn't really have a "parent"--in that case, the value "1" is returned in ioDrParID. The value "1" is a "pseudo" directory ID which always means "the parent of the root directory." There is never any real directory which has that ID number.

ioFlClpSiz (files only)

The clump size to be used when writing the file; if it's 0, the volume's clump size is used when the file is opened.