The Storage Kit Table of Contents | The Storage Kit Index |
Declared in: be/storage/NodeMonitor.h
Library: libbe.so
The Node Monitor is a service that lets you ask to be notified of certain file system changes. You can ask to be told when a change is made to...
You can also ask to be notified when...
|
When something interesting happens, the Node Monitor lets you know by sending a BMessage to the target of your choice.
There are two Node Monitor functions, watch_node() and stop_watching(). The names are a wee bit misleading, so before we go on to the full technical descriptions, let's nip some buds:
|
watch_node() tells the Node Monitor to...
|
Jumping ahead a bit, here's a sample function that tells the Node Monitor to watch for name and attribute changes to a given entry. The Monitor's notifications will be sent to the application's main loop:
status_t WatchThis(BEntry *entry) { node_ref nref; entry->GetNodeRef(&nref); return (watch_node(&nref, B_WATCH_NAME | B_WATCH_ATTR, be_app_messenger)); }
watch_node()'s flags argument is a combination of the following
There's one other constant, which lives in a class by itself:
You can't combine B_STOP_WATCHING with any of the others in an attempt to stop watching a specific category of changes. For example, if you call...
watch_node(&nref, B_WATCH_STAT, be_app_messenger); watch_node(&nref, B_WATCH_ATTR, be_app_messenger);
...and then call...
watch_node(&nref, B_STOP_WATCHING, be_app_messenger);
...both of the previous Monitor calls are stopped.
|
If you can, you should combine as many flags as you're going to need in single calls to watch_node(). Recall the example used above:
watch_node(&nref, B_WATCH_NAME | B_WATCH_ATTR, be_app_messenger);
This is better than making separate watch_node() calls (one to pass B_WATCH_NAME and another to pass B_WATCH_ATTR)—not only because the single call is naturally more efficient than two, but also because the Node Monitor can only monitor 4096 nodes per team at a time. Every call to watch_node() consumes a Node Monitor slot, even if you're already monitoring the requested node.
If you want to watch all aspects of a node, just pass B_WATCH_ALL to every watch_node() call. This will consume only a single Node Monitor slot.
A BMessage notification sent by the Node Monitor looks like this:
The "opcode" constants and additional fields are described in "Opcode Constants." In general, the opcodes correspond to the flags that you passed to watch_node(); however, this correspondence isn't always one-to-one.
There are seven opcode constants:
B_ENTRY_CREATED |
B_ENTRY_REMOVED |
B_ENTRY_MOVED |
B_STAT_CHANGED |
B_ATTR_CHANGED |
B_DEVICE_MOUNTED |
B_DEVICE_UNMOUNTED |
RETURN CODES
|
Tells the Node Monitor to stop sending notifications to the target described by the arguments. All the Node Monitor "slots" that were allocated to the target are freed. Keep in mind that are only 4096 slots for the entire system.
RETURN CODES
The following sections describe the "opcode" constants; these are the values that appear in the "opcode" field of the BMessages that are generated by the Node Monitor. Note that in these descriptions, the use of the terms "entry" and "node" is sometimes blurred.
You get this notification if you applied B_WATCH_DIRECTORY to the directory in which the entry was created. The message's fields are:
Field | Type code | Description |
---|---|---|
"opcode" | B_INT32_TYPE | B_ENTRY_CREATED |
"name" | B_STRING_TYPE | The name of the new entry. |
"directory" | B_INT64_TYPE | The ino_t (node) number for the directory in which the entry was created. |
"device" | B_INT32_TYPE | The dev_t number of the device on which the new entry resides. |
In your code, you would parse a B_ENTRY_CREATED message like this:
void MyTarget::MessageReceived(BMessage *msg) { int32 opcode; dev_t device; ino_t directory; ino_t node; const char *name; if (msg->what == B_NODE_MONITOR) { if (msg->FindInt32("opcode", &opcode) == B_OK) { switch (opcode) { case B_ENTRY_CREATED: msg->FindInt32("device", &device); msg->FindInt64("directory", &directory); msg->FindInt64("node", &node); msg->FindString("name", &name); break; ...
So, what do you do with these fields?
The "device", "directory", and "name" fields can be used to create an entry_ref to the new entry:
entry_ref ref; const char *name; ... msg->FindInt32("device", &ref.device); msg->FindInt64("directory", &ref.directory); msg->FindString("name", &name); ref.set_name(name);
If you want to start Node Monitoring the new entry (or, more accurately, the node of the new entry), you stuff "device" and "directory" into a node_ref:
node_ref nref; status_t err; ... msg->FindInt32("device", &nref.device); msg->FindInt64("node", &nref.node); err = watch_node(&nref, B_WATCH_ALL, be_app_messenger);
Note that the "directory" field is a node number. By combining this number with the "device" field, you can create a node_ref that points to the entry's parent. From there, you're a SetTo() away from a BDirectory object:
node_ref nref; BDirectory dir; status_t err; ... msg->FindInt32("device", &nref.device); msg->FindInt64("directory", &nref.node); err = dir.SetTo(&nref);
You get this if you applied B_WATCH_NAME on the node itself, or B_WATCH_DIRECTORY on the directory that the node lived in. The message's fields are:
Field | Type code | Description |
---|---|---|
"opcode" | B_INT32_TYPE | B_ENTRY_REMOVED |
"directory" | B_INT64_TYPE | The ino_t (node) number of the directory from which the entry was removed. |
"device" | B_INT32_TYPE | The dev_t number of the device that the removed node used to live on. |
"node" | B_INT64_TYPE | The ino_t number of the node that was removed. |
|
Parsing the message is the same as for B_ENTRY_CREATED, but without the "name" field. See "Parsing and Tricks," above.
Note that the B_ENTRY_REMOVED message is sent as soon as the node's entry is "unlinked" from its directory. The node itself may linger for while after that. Follow this logic:
You can take advantage of this to warn a user that a file is going to go away, or to make a backup, or whatever. For example, let's say you have an application that lets the user open files; each time a file is opened, your OpenFile() function creates a BFile object and starts the Node Monitor running:
status_t YourApp::OpenFile(const char *pathname) { BFile *file; node_ref nref; status_t err; file = new BFile(pathname, B_READ_WRITE); if ((err=file->InitCheck()) != B_OK) return err; file->GetNodeRef(&nref); err = watch_node(&nref, B_WATCH_NAME, be_app_messenger); if (err != B_OK) { delete file; return err; } /* We>ve got the file and we>re monitoring it; now we cache * the BFile by adding it to a BList (data member). * function. There>s a race condition between the * watch_node() call above and the following AddItem(). */ return ((FileList->AddItem((void *)file)) ? B_OK : B_ERROR); }
Now we receive a Node Monitor message telling us the node has been removed. We stuff the "device" and "node" fields into a node_ref and pass them to a (fictitious) AlertUser() function:
void YourApp::MessageReceived(BMessage *msg) { int32 opcode; node_ref nref; if (msg->what == B_NODE_MONITOR) { if (msg->FindInt32("opcode", &opcode) == B_OK) { switch (opcode) { case B_ENTRY_REMOVED: msg->FindInt32("device", &nref.device); msg->FindInt64("node", &nref.node); GoodbyeFile(nref); ... }
The implementation of GoodbyeFile() (which we won't show here) would walk down the BFile list looking for a node_ref that matches the argument:
void YourApp::GoodbyeFile(node_ref nref) { BFile *filePtr; int32 ktr = 0; node_ref cref; while ((*filePtr = (BFile *)FileList->ItemAt(ktr++))) { filePtr->GetNodeRef(&cref); if (nref == cref) { /* We found it. Now we do whatever * we need to do. */ } } }
If a match is found, your app could then do whatever it needs to do. Remember—the node's data is still valid until your BFile is destroyed or re-initialized.
You get this if you applied B_WATCH_NAME on the node itself, or B_WATCH_DIRECTORY on either of the directories. The message's fields are:
Field | Type code | Description |
---|---|---|
"opcode" | B_INT32_TYPE | B_ENTRY_MOVED |
"name" | B_STRING_TYPE | The name of the entry that moved. |
"from directory" | B_INT64_TYPE | The ino_t (node) number of the directory from that the node was removed from. |
"to directory" | B_INT64_TYPE | The ino_t (node) number of the directory that the node was added to. |
"device" | B_INT32_TYPE | The dev_t number of the device that the moved node entry lives on. (You can't move a file between devices, so this value will be apply to the file's old and new locations.) |
"node" | B_INT64_TYPE | The ino_t number of the node that moved. |
|
Parsing the message is much the same as for B_ENTRY_CREATED, modulo the directory field changes. See "Parsing and Tricks."
Moving a node doesn't affect the objects that hold the node open. They (the objects) can continue to read and write data from the node.
You get this if you applied B_WATCH_STAT on the node itself. The message's fields are:
Field | Type code | Description |
---|---|---|
"opcode" | B_INT32_TYPE | B_STAT_CHANGED |
"node" | B_INT64_TYPE | The ino_t number of the node. |
"device" | B_INT32_TYPE | The dev_t number of the node's device. |
The stat structure is described in "The stat Structure" on page155 in the BStatable class. The fields that you can change are:
A couple of important points:
In most uses of the B_STAT_CHANGED message, you have to cache the objects that you're monitoring so you can compare their node_refs to the message fields (an example of this is given in B_ENTRY_REMOVED). Furthermore, you may want to cache the objects' stat structures so you can figure out which field changed.
You get this if you applied B_WATCH_ATTR on the node itself. The message's fields are:
Field | Type code | Description |
---|---|---|
"opcode" | B_INT32_TYPE | B_ATTR_CHANGED |
"node" | B_INT64_TYPE | The ino_t number of the node. |
"device" | B_INT32_TYPE | The dev_t number of the node's device. |
Attributes are key/value pairs that can be "attached" to any file (regardless of flavor). They're described in the BNode class.
As with B_STAT_CHANGED messages, you may not be able to use the B_ATTR_CHANGED information directly. Instead, you have to cache references to the (BNode) objects that you're monitoring so you can compare their node_refs to the message fields (an example of this is given in B_ENTRY_REMOVED).
You get this if you passed B_WATCH_MOUNT to watch_node(). The message's fields are:
Field | Type code | Description |
---|---|---|
"opcode" | B_INT32_TYPE | B_DEVICE_MOUNTED |
"new device" | B_INT32_TYPE | The dev_t number of the newly-mounted device. |
"device" | B_INT32_TYPE | The dev_t number of the device that holds the directory of the new device's mount point. |
"directory" | B_INT64_TYPE | The ino_t (node) number of the directory that acts as the new device's mount point. |
Obviously, there's no node involved, here, so the first argument to the watch_node() call can be NULL:
watch_node(NULL, B_WATCH_MOUNT, be_app_messenger);
Unlike with the other "watch flags," the only way to stop the mount-watching is to call stop_watching().
You get this if you passed B_WATCH_MOUNT to watch_node(). The message's fields are:
Field | Type code | Description |
---|---|---|
"opcode" | B_INT32_TYPE | B_DEVICE_UNMOUNTED |
"device" | B_INT32_TYPE | The dev_t number of the unmounted device. |
Be careful with the device number: dev_ts are quickly recycled. You should only need this number if you're keeping a list of the dev_ts of all mounted disks and you want to remove the dev_t for this recently-unmounted volume (keeping in mind that a device mounted message bearing this dev_t may arrive in the meantime).
The Storage Kit Table of Contents | The Storage Kit Index |
Copyright © 2000 Be, Inc. All rights reserved..