The Storage Kit: BNode

The Storage Kit Table of Contents | The Storage Kit Index

# BNode

Derived from: BStatable

Declared in: be/storage/Node.h

Library: libbe.so

###### Summary

The BNode class gives you access to the data that a file system entry (a file, directory, or symbolic link) contains. There are two parts to this data:

- There's the "data portion" itself...

- ...and then there are the node's attributes.

The content of the data portion depends on the node's flavor:

- If it's a regular file, the data is whatever it is that the file is meant to contain: ASCII text, binary image or sound data, executable code, and so on. Note that resources (as created by the BResources class) are kept in the data portion.

- If it's a directory, the data is the list of entries that the directory contains.

- If it's a symbolic link, the data is the path of the "linked-to" file. The path can be absolute or relative.

The content of the attributes, on the other hand, isn't qualified by the node's flavor: Any node can contain any set of attributes.

### Nodes are Dumb

Keep in mind that the concept of a "node" designates the data parts (data and attributes) of a file (a file, directory, or link). Contrast this with an "entry," which designates the entity's location within the file system: For example, you can write to a "node" (but not an entry), and you can rename an "entry" (but not a node).

This isn't just a conceptual crutch, it's the law: Nodes really don't know where they're located. For example, you can't ask a node for its name, or for the identity of its parent. This has some serious implications, the most important of which is...

- If you need to store a reference to a file (or directory, or symbolic link), don't store the node—in other words, don't cache the BNode object. Instead, store the information that you used to create the BNode (typically, a pathname or `entry_ref` structure).

Now that we've got that straight, we'll relax the rules a bit:

- BDirectory objects are node/entry hybrids. A BDirectory does know its own name (and parent, and so on).

This doesn't really change the "store the info" rule. Even if you're dealing exclusively with BDirectory objects, you should keep the generative information around. The primary reason for this is...

### The "Node Pool" is Limited (File Descriptors)

Every BNode object consumes a "file descriptor." Your application can only maintain 256 file descriptors at a time. Because of this limit, you shouldn't keep BNodes around that you don't need. Keep in mind that BEntry objects also consumes file descriptors (one per object).

| The file descriptor limit will probably be lifted, or at least settable, in a subsequent release. But even then you should be frugal.

### Derived Classes and their Uses

BNode has three derived classes: BFile, BDirectory, and BSymLink. The derived classes define functions that let you access the node's data portion in the appropriate style; for example...

- BFile implements `Read()` and `Write()` functions that let you retrieve arbitrary amounts of data from arbitrary positions in the file.

- BDirectory implements functions, such as `GetNextEntry()` and `FindEntry()`, that read entries from the directory.

- BSymLink's `ReadLink()` returns the pathname that it contains.

If you want to (sensibly) look at a node's data portion, you must create an instance of the appropriate derived class. In other words, if you want to browse a directory, you have to create a BDirectory instance; if you want to write to a file, you create a BFile.

Be aware that it's not (always) an error to create an instance of the "wrong" derived class; setting a BFile to a symbolic link, for example, will traverse the link such that the BFile opens the file that the symbolic link is linked to. See the individual derived class specifications for more information.

### BNode Instances

In practice, you almost always want to create an instance of one of the BNode-derived classes; but if, for whatever reason, you find yourself holding a BNode instance, here's what you'll be able to do with it:

- Read and write attributes. The attribute-accessing functions (`ReadAttr()`, `WriteAttr()`, and so on) are general—they work without regard for the node's flavor. Thus, you don't need an instance of a specific derived class to read and write attributes.

- Get `stat information.` The BStatable functions can be invoked on any flavor of node.

- Lock the node. This prevents other "agents" (other objects, other apps, the user) from accessing reading or writing the node's data and attributes. See "Node Locking".

### Converting a BNode to an Instance of a Derived Class

| This section describes situations and presents solutions to problems that are a bit esoteric. If you never create direct instances of BNode (and you never have to), then you should skip this and go to "Node Locking".

There may be times when you find yourself holding on to a BNode (instance) that you want to convert into a BFile, BDirectory, or BSymLink. However, you can't go directly from a BNode instance to an instance of BFile, BDirectory, or BSymLink—you can't tell your BNode to "cast itself" as one of its children.

There are solutions, however...

#### Converting to BDirectory

Converting from a BNode to a BDirectory, while not transparent, is pretty simple: Grab the `node_ref` out of the BNode and pass it to the BDirectory constructor or `SetTo()` function. Regard this example function:

       void Node2Directory(BNode *node, BDirectory *dir)
       {
          node_ref nref;

          if (!node || !dir) {
             dir.Unset();
             return;
          }

          node.GetNodeRef(&nref);

          /* Set the BDirectory.  If nref isn>t a directory node,
           * the SetTo() will fail.
           */
           dir.SetTo(&nref);
       }

#### Converting to BFile or BSymLink

Converting a BNode instance to a BFile or BSymLink isn't as neat as the foregoing. Instead, you have to cache the information that you used to initialize the BNode in the first place, and then reuse it to create the BFile or BSymLink.

For example, let's say you receive an `entry_ref`. You turn it into a BNode, but then decide you need the data-writing power of a BFile. If, in the meantime, you lost the original `entry_ref`, you're sunk—there's nothing you can do.

### Node Locking

Another feature provided by the BNode class is "node locking": Through BNode's `Lock()` function you can restrict access to the node. The lock is removed when `Unlock()` is called, or when the BNode object is deleted.

- When you lock a node, you prevent other objects (or agents) from reading or writing the node's data and attributes. No other agent can even open the node—other BNode constructions and POSIX open() calls (on that node) will fail while you hold the lock.

- You can only acquire a node lock if there are no file descriptors open on the node (with one exception). This means that no other BNode may be open on the node (locked or not), nor may the node be held open because of a POSIX `open()` (or `opendir()`) call.

The one exception to the no-file descriptors rule has to do with BEntries: Let's say you lock a directory, and then you initialize a BEntry to point to an entry within that directory. Even though the BEntry creates a file descriptor to the directory (as explained in the BEntry class), the initialization will succeed.

#### Implications

For files (and, less importantly, symlinks), the implications of locking are pretty clear: No one else can read or write the file. For directories, it's worth a closer look:

- Locking a directory means that the contents of the directory can't change: You can't create new nodes in the directory, or rename or remove existing ones. (You can, however, create abstract entries within the directory; see BEntry for more on abstract entries.)

Locking a node does not lock the node's entry: You can't "lock out" entry operations, such as rename, move, and remove. Even if you have a node locked, the entry that acts as the "container" for that node could disappear. If you want to prevent such operations on a node's entry, lock the entry's parent directory.

In general, you should try to avoid locking your nodes. If you must lock, try to make it brief. The primary reason (and, pretty much, the only reason) to lock is if separate elements in the data and/or attributes must be kept in a consistent state. In such a case, you should hold the lock just long enough to ensure consistency.

| You shouldn't use locks to "privatize" data. Locking isn't meant to be used as a heightened permissions bit.

## Constructor and Destructor

### BNode()

`BNode(`const entry_ref *ref`) `
`BNode(`const BEntry *entry`) `
`BNode(`const char *path`) `
`BNode(`const BDirectory *dir, const char *path`) `
`BNode() `
`BNode(`const BNode &node`) `

Creates a new BNode object that's initialized to represent a specific file system node. To retrieve the status of the initialization, call `InitCheck()` immediately after constructing the object:

       BNode node("/boot/lbj/FidoOnFire.gif");
       if (node.InitCheck() != B_OK)
          /* The object wasn>t initialized. */

A successfully initialized BNode object creates a "file descriptor" through which the object reads and writes the node's data and attributes. You can only have 256 file descriptors at a time (per application). The object's file descriptor is closed when the object is deleted, reset (through `SetTo()`), or unset (`Unset()`).

- Default constructor. The object's status will be `B_NO_INIT,` and the file descriptor isn't allocated until you actually initialize the object with a call to `SetTo()`.

- Copy constructor. The new BNode is set to the same node as the argument. Each of the two BNode objects has its own file descriptor.

- Other constructors. See the `SetTo()` functions.

### ~BNode()

virtual ~`BNode() `

Frees the object's file descriptor, unlocks the node (if it was locked), and destroys the object.

## Member Functions

### GetAttrInfo() , attr_info

status_t`GetAttrInfo(`const char *attr, attr_info *info`)` const
typedef struct `attr_info` {}

Gets information about the attribute named by attr. The information is copied into info, which must be allocated before it's passed in.

The `attr_info` structure, defined in be/kernel/fs_attr.h, is

       typedef struct attr_info
       {
          uint32 type;
          off_t size;
       } attr_info;

- `type` is a constant (`B_STRING_TYPE`, `B_INT32_TYPE`, etc) that describes the type of data that the attribute holds.

- `size` is the size of the attribute's data, in bytes.

RETURN CODES

- `B_OK`. Success.

- `B_ENTRY_NOT_FOUND`. The node doesn't have an attribute named attr.

- `B_FILE_ERROR`. The object is uninitialized.

### GetNextAttrName() , RewindAttrs()

status_t`GetNextAttrName(`char *buffer`)`
status_t`RewindAttrs(`void`)`

Every BNode maintains a pointer into its list of attributes. `GetNextAttrName()` retrieves the name of the attribute that the pointer is currently pointing to, and then bumps the pointer to the next attribute. The name is copied into the buffer, which should be at least `B_ATTR_NAME_LENGTH` characters long. The copied name is `NULL`-terminated. When you've asked for every name in the list, `GetNextAttrName()` returns an error.

| `GetNextAttrName()` does not clear its argument if it returns an error. This will be corrected in a subsequent release.

`RewindAttrs()` resets the BNode's attribute pointer to the first element in the list.

To visit every attribute name, you would do something like this:

       /* Print every attribute name. */
       char buf[B_ATTR_NAME_LENGTH];

       while (node.GetNextAttrName(buf) == B_OK) {
          printf("> Attr name:  %sn", buf);
       }

The attribute list is not static; when you ask for the next attribute name, you're asking for the next name in the list as it exists right now.

Furthermore, the ordinal position of an attribute within the list is indeterminate. "Newer" attributes are not necessarily added to the end of the list: If you alter the list while you're walking through it, you may get curious results—you may not see the attribute that you just now added (for example).

In general, it's best to avoid altering the list while you're iterating over it.

RETURN CODES

- `B_OK`. Success.

- `B_ENTRY_NOT_FOUND`. You've hit the end of the list.

- `B_FILE_ERROR`. The object is uninitialized.

### InitCheck()

status_t `InitCheck(`void`)` const

Returns the status of the most recent initialization.

RETURN CODES

- `B_OK`. The object was successfully initialized.

- `B_NO_INIT`. The object is uninitialized.

- See the `SetTo()` function for a list other return values.

### Lock() , Unlock()

status_t `Lock(`void`) `
status_t `Unlock(`void`) `

Locks and unlocks the BNode's node. While the node is locked, no other object can access the node's data or attributes. More precisely, no other agent can create a file descriptor to the node. If a file descriptor already exists to this node, the `Lock()` function fails.

See "Node Locking" for details.

RETURN CODES

- `B_OK`. The node was successfully locked or unlocked.

- `B_BUSY`. (`Lock()`) The node can't be locked.

- `B_BAD_VALUE`. (`Unlock()`) The node isn't locked.

- `B_FILE_ERROR`. The object is uninitialized.

### ReadAttr() , WriteAttr() , RemoveAttr()

ssize_t `ReadAttr(`const char *name,
type_code type,
off_t offset,
void *buffer,
size_t length`)

`

ssize_t `WriteAttr(`const char *name,
type_code type,
off_t offset,
const void *buffer,
size_t length`)

`

status_t `RemoveAttr(`const char *attr`) `

These functions read, write, and remove the node's attributes. Attributes are name/data pairs, where names must be unique (within a given node) and the data can be of arbitrary length.

`ReadAttr()` reads the data in the attribute named name, and copies it in buffer. The length of the buffer (the maximum number of bytes to copy) is given by length. Currently, the type and offset arguments are unused (or unreliable). The function returns the number of bytes that were actually read.

`WriteAttr()` erases the data currently held by name (if such an attribute exists) and replaces it with a copy of the first length bytes of data in buffer. The type argument is remembered—you can retrieve an attribute's type through `GetAttrInfo()`, for example—and you need to specify the correct type when you're forming a query (see BQuery and the note below). But, as mentioned above, you don't need to match types when you're reading the attribute. The offset argument is currently unreliable and shouldn't be used. The functions returns the number of bytes that were written.

| If you want to use the attribute in a query, its type must be either string, `int32`, `uint32`, `int64`, `uint64`, `double`, or `float`. (In other words, type must be `B_STRING_TYPE`, or `B_INT32_TYPE`, or `B_UINT32_TYPE`, and so on.)

| The value of an indexed attribute must be no more than 255 bytes long.

`RemoveAttr()` deletes the attribute given by name.

RETURN CODES

`ReadAttr()` and `WriteAttr()`, if successful, return the number of bytes read or written.

- `B_OK`. (`Remove`) The attribute was successfully removed.

- `B_ENTRY_NOT_FOUND`. (`ReadAttr()` and `Remove()`) The attribute doesn't exist.

- `B_FILE_ERROR`. The object is uninitialized.

- `B_FILE_ERROR`. (`WriteAttr()` and `Remove()`) This object is a read-only BFile.

- `B_NOT_ALLOWED`. (`WriteAttr()` and `Remove()`) The node is on a read-only volume.

- `B_DEVICE_FULL`. (`WriteAttr()`) Out of disk space.

- `B_NO_MEMORY`. (`WriteAttr()`) Not enough memory to complete the operation.

### RemoveAttr() see ReadAttr()

### RenameAttr()

status_t `RenameAttr(`const char *name, const char *new_name`) `

Moves the attribute given by name to new_name. If new_name exists, it's clobbered.

RETURN CODES

- `B_OK`. The attribute was successfully renamed.

- `B_ENTRY_NOT_FOUND`. The name attribute doesn't exist.

- `B_FILE_ERROR`. The object is uninitialized.

- `B_FILE_ERROR`. This object is a read-only BFile.

- `B_NOT_ALLOWED`. The node is on a read-only volume.

### RewindAttrs see GetNextAttrName()

### SetTo() , Unset()

status_t `SetTo(`const entry_ref *ref`) `
status_t `SetTo(`const BEntry *entry`) `
status_t `SetTo(`const char *path`) `
status_t `SetTo(`const BDirectory *dir, const char *path`) `
void `Unset(`void`) `

Closes the BNode's current file descriptor and opens it on the node (of the entry) that's designated by the arguments.

- In the path version, path can be absolute or relative, and can contain "." and ".." elements. If path is relative, it's reckoned off of the current working directory.

- In the dir/path version, path must be relative. It's reckoned off of the directory given by dir.

BNode instances never traverse symbolic links. If the designated entry is a symbolic link, the BNode will open the link's node. (Conversely, BFile instances always traverse symbolic links.)

`Unset()` closes the BNode's file descriptor and sets `InitCheck()` to `B_NO_INIT`.

RETURN CODES

- `B_OK`. All is well.

- `B_ENTRY_NOT_FOUND`. The designated entry doesn't exist.

- `B_BAD_VALUE`. Uninitialized or malformed argument.

- `B_BUSY`. The node is locked.

### Sync()

status_t `Sync(`void`) `

Immediately performs any pending disk transactions for the file, returning `B_OK` on success and an appropriate error message otherwise.

### Unlock() see Lock()

### Unset() see SetTo()

### WriteAttr() see ReadAttr()

## Operators

### = (assignment)

BNode& operator`=(`const BNode &node`) `

In the expression

       BNode a = b;

BNode a is initialized to refer to the same node as b. To gauge the success of the assignment, you should call `InitCheck()` immediately afterwards. It's safe to assign a BNode to itself.

### == , != (comparison)

bool operator`==(`const BNode &node`)` const
bool operator`!=(`const BNode &node`)` const

Two BNode objects are said to be equal if they're set to the same node, or if they're both `B_NO_INIT`.

The Storage Kit Table of Contents | The Storage Kit Index

The Be Book,
...in lovely HTML...
for BeOS Release 5.

Copyright © 2000 Be, Inc. All rights reserved..