Chapter 8. Monitoring Changes to Files and Directories
Prev | Part I. Getting the Right Look and Feel | Next

## Chapter 8. Monitoring Changes to Files and Directories

The File Alteration Monitor (FAM) monitors changes to files and directories in the filesystem and notifies interested applications of these changes. Your application can use FAM to get an up-to-date view of the filesystem rather than having to poll the filesystem. This chapter describes the required libraries and provides a basic list of steps for using FAM. For more detailed information, refer to the `fam(1M)` and `FAM(3X)` reference pages.

This chapter contains these sections:

- “FAM Overview” provides an overview to FAM including the libraries and header files needed to use FAM in your application.

- “The FAM Interface” describes the FAM API.

- “Using FAM” provides a simple example demonstrating FAM.

## FAM Overview

Typically, if applications need to monitor the status of a file or directory, they must periodically poll the filesystem. FAM provides a more efficient and convenient method.

FAM consists of the FAM daemon, `fam`, and a library for interacting with this daemon. An application can request `fam` to monitor any files or directories in the filesystem. When `fam` detects changes to these files, it notifies the application.

This section provides an overview of FAM and describes:

- “Theory of Operation”

- “FAM Libraries and Include Files”

### Theory of Operation

FAM uses `imon`, a pseudo device, to monitor filesystem activity on your system on a file-by-file basis. You can refer to the `imon(7)` reference page for more information on its operation, but you should not attempt to access `imon` directly.

When you provide FAM with the name of a file or directory to monitor, FAM passes the request to `imon`, which begins monitoring the inode corresponding to the pathname. When `imon` detects a change to an inode that it is monitoring, it notifies FAM, which matches the inode to a corresponding filename. FAM then generates a FAM event on a socket. Your application can either monitor the socket or periodically poll FAM to detect FAM events.

This difference between FAM and `imon` can produce some unexpected results. For example, if a user moves a file, FAM reports that the file is deleted. The reason is that FAM monitors files by name and not inode, so it doesn't know that the file still exists.

| Note: Unlike local files and directories, FAM monitors NFS-mounted files and directories by name rather than by inode.

As another example, consider the case where FAM is monitoring a file. If the user deletes the file, FAM correctly reports that fact. Then FAM polls the directory every few seconds to see if the file has been created. If you need to detect the creation of a given file by name, you may want to monitor the directory in which it will be created and watch for FAM events notifying the creation of a file by that name in the directory.

Whenever FAM is asked to monitor a file/directory that resides on a remote (NFS) filesystem, FAM tries to make a connection to the FAM on the NFS server. If it succeeds, it asks the server fam to monitor the file. The server FAM sends FAM events, and the original FAM translates those events to a form its client can use. If FAM can't connect to FAM on the server, it monitors the file itself by polling every few seconds. Polling over NFS has a high overhead.

### FAM Libraries and Include Files

The FAM interface routines are in the `libfam` library. `libfam` depends on the `libC` library. Be sure to specify `-lfam` before `-lC` in the compilation or linking command. If you are using fam from a C++ program, `libC` is included automatically. You must include `libC` if you are using fam from a C program.

You must include `<fam.h>` in any source file that uses FAM. You must also include `<sys/select.h>` if you use the `select(2)` system call.

## The FAM Interface

This section describes the functions you use to access FAM from your application:

- “Opening and Closing a FAM Connection”

- “Monitoring a File or Directory”

- “Suspending, Resuming, and Canceling Monitoring”

- “Detecting Changes to Files and Directories”

- “FAM Examples”

### Opening and Closing a FAM Connection

The function `FAMOpen()` opens a connection to `fam`:

    int FAMOpen(FAMConnection* fc)

`FAMOpen()` returns 0 if successful and -1 if unsuccessful. `FAMOpen()` initializes the FAMConnection structure passed to it, which you must use in all subsequent FAM procedure calls in your application.

An element of the FAMConnection structure is the file descriptor associated with the socket that FAM uses to communicate with your application. You need this file descriptor to perform `select()` operations on the socket. You can obtain the file descriptor using the `FAMCONNECTION_GETFD()` macro:

    FAMCONNECTION_GETFD(fc)

The function `FAMOpen2` tells FAM the application's name:

    int FAMOpen2(FAMConnection* fc, const char* appName)

FAM uses appName when it prints debugging messages.

The function `FAMClose()` closes a connection to `fam`:

    int FAMClose(FAMConnection* fc)

`FAMClose()` returns 0 if successful and -1 if unsuccessful.

### Monitoring a File or Directory

`FAMMonitorDirectory()` and `FAMMonitorFile()` tell FAM to start monitoring a directory or file respectively:

    int FAMMonitorDirectory(FAMConnection *fc,
                            char *filename,
                            FAMRequest* fr,
                            void* userData)

    int FAMMonitorFile(FAMConnection *fc,
                       char *filename,
                       FAMRequest* fr,
                       void* userData)

`FAMMonitorDirectory()` monitors not only changes that happens to the contents of the specified directory file, but also to the files in the directory. If the directory contains subdirectories, `FAMMonitorDirectory()` monitors changes to the subdirectory files, but not the contents of those subdirectories. `FAMMonitorFile()` monitors only what happens to the specified file. Both functions return 0 if successful and -1 otherwise.

The first argument to these functions is the FAMConnection structure initialized by `FAMOpen()`. The second argument is the full pathname of the directory or file to monitor. Note that you can't use relative pathnames.

The third argument is a FAMRequest structure that these functions initialize. You can pass this structure to `FAMSuspendMonitor()`, `FAMResumeMonitor()`, or `FAMCancelMonitor()` to respectively suspend, resume, or cancel the monitoring of the file or directory. “Suspending, Resuming, and Canceling Monitoring” further describes these functions.

The fourth argument is a pointer to any arbitrary user data that you want included in the FAMEvent structure returned by `FAMNextEvent()` when this file or directory changes.

FAM then generates FAM events whenever it detects changes in monitored files or directories. “Detecting Changes to Files and Directories” describes how to detect and interpret these events.

Two similar routines are `FAMMonitorDirectory2()` and `FAMMonitorFile2()`:

    int FAMMonitorDirectory2(FAMConnection *fc,
                            char *filename,
                            FAMRequest* fr);

    int FAMMonitorFile2(FAMConnection *fc,
                       char *filename,
                       FAMRequest* fr);

In these routines, the caller picks the request number, not `libfam`. The caller specifies the request number by putting it in the FAMRequest before calling the routine. For example:

    FAMConnection fc;
    FAMRequest frq;
    ...
    frq.reqnum = some_number_associated_with_tmp;
    if (FAMMonitorDirectory2(&fc, “/tmp”, &frq) < 0)
        perror(“can't monitor /tmp”);

If you use the -2 routines, you must choose unique request numbers. See FAMAcknowledge below.

It's up to you to determine which routines to use: the -2 routines or the original routines.

### Suspending, Resuming, and Canceling Monitoring

Once you've begun monitoring a file or directory, you can cancel monitoring or temporarily suspend and later resume monitoring.

`FAMSuspendMonitor()` temporarily suspends monitoring a file or directory. `FAMResumeMonitor()` resumes monitoring the file or directory. Suspending file monitoring can be useful when your application does not need to display information about a file (for example, when your application is iconified).

| Note: FAM queues any changes that occur to the file or directory while monitoring is suspended. When your application resumes monitoring, FAM notifies it of any changes.

The syntax for these functions is:

    int FAMSuspendMonitor(FAMConnection *fc, FAMRequest *fr);

    int FAMResumeMonitor(FAMConnection *fc, FAMRequest *fr);

fc is the FAMConnection returned by `FAMOpen()`, and fr is the FAMRequest returned by either `FAMMonitorFile()` or `FAMMonitorDirectory()`. Both functions return 0 if successful and -1 otherwise.

When your application is finished monitoring a file or directory, it should call `FAMCancelMonitor()`:

    int FAMCancelMonitor(FAMConnection *fc, FAMRequest *fr)

`FAMCancelMonitor()` instructs FAM to no longer monitor the file or directory specified by fr. It returns 0 if successful and -1 otherwise.

After you call `FAMCancelMonitor()`, FAM sends a FAMAcknowledge event. When you've seen the FAMAcknowledge event, you know it's safe to re-use the request number (if you're using the -2 form monitoring routines).

### Detecting Changes to Files and Directories

Whenever FAM detects changes in files or directories that it is monitoring, it generates a FAM event. Your application can receive FAM events in one of two ways:

The Select approach
| Your application performs a `select`(2) on the file descriptor in the FAMConnection structure returned by `FAMOpen()`. When this file descriptor becomes active, the application calls `FAMNextEvent()` to retrieve the pending FAM event.
The Polling approach
| Your application periodically calls `FAMPending()` (typically when the system is waiting for input). When `FAMPending()` returns with a positive return value, your application calls `FAMNextEvent()` to retrieve the pending FAM events.

`FAMPending()` has the following syntax:

    int FAMPending(FAMConnection *fc)

It returns 1 if there is a FAM event queued, 0 if there is no queued event, and -1 if there is an error. `FAMPending()` returns immediately (that is, it does not wait for an event).

Once you have determined that there is a FAM event queued, whether by using the select or polling approach, call `FAMNextEvent()` to retrieve it:

    int FAMNextEvent(FAMConnection *fc, FAMEvent *fe)

`FAMNextEvent()` returns 0 if successful and -1 if there is an error. The first argument to `FAMNextEvent()` is the FAMConnection structure initialized by `FAMOpen()`. The second argument is a pointer to a FAMEvent structure, which `FAMNextEvent()` fills in with information about the FAM event. The format of the FAMEvent structure is:

    typedef struct {
        FAMConnection* fc;
        FAMRequest fr;
        char *hostname;
        char filename[PATH_MAX];
        void *userdata;
        FAMCodes code;
        } FAMEvent;

fc is the FAMConnection structure initialized by `FAMOpen()`.

fr is the FAMRequest structure returned by either `FAMMonitorFile()` or `FAMMonitorDirectory()` when you requested that FAM monitor the file or directory that changed.

hostname is an obsolete field. Don't use it in your applications.

filename is either the full pathname of the file or directory that you monitored or the name of a file in a directory that you monitored.

userdata is the arbitrary data pointer that you provided when you called either `FAMMonitorFile()` or `FAMMonitorDirectory()` to monitor this file or directory. If you used the -2 routine, `FAMMonitorDirectory2()` or `FAMMonitorFile2()`, userdata is undefined.

code is an enumerated value of type FAMCodes that describes the change that occurred. It can take any of the following values:

FAMChanged |  | Some value of the file or directory that can be obtained with `lstat`(2) changed.
FAMDeleted |  | A file or directory being monitored was deleted.
FAMStartExecuting |  | A monitored, executable file started executing. The event occurs only the first time the file is executed.
FAMStopExecuting |  | A monitored, executable file that was running finished. If multiple processes from an executable are running, this event is generated only when the last one finishes.
FAMCreated |  | A file was created in a directory being monitored.
FAMAcknowledge |  | FAM generates a FAMAcknowledge event in response to a call to `FAMCancelMonitor()`. If you specify an invalid request, that is, a relative path, FAM automatically cancels the request and immediately sends a FAMAcknowledge event.
FAMExists |  | When the application requests that a file be monitored, FAM generates a FAMExists event for that file (if it exists). When the application requests that a directory be monitored, FAM generates a FAMExists event for that directory (if it exists) and every file contained in that directory.
FAMEndExist |  | When the application requests a file or directory be monitored, FAM generates a FAMEndExist event after the last FAMExists event. (Therefore if you monitor a file, FAM generates a single FAMExists event followed by a FAMEndExist event.)
| Note: Prior to IRIX 6.2, `FAMNextEvent()` did not initialize the filename field in a FAMEndExist event. You should use the request number to find the file or directory these events reference.

#### Symbolic Links

If you specify the pathname of a symbolic link to `FAMMonitorDirectory()` or `FAMMonitorFile()`, FAM monitors only the symbolic link itself, not the target of the link. Although it might seem logical to automatically monitor the target of a symbolic link, consider that if the target is on an automounted filesystem, monitoring the target triggers and holds an automount. Another reason to monitor the link instead of the target is that the target may not exist.

There is no general solution for monitoring targets of symbolic links. You might decide that it's appropriate for your application to monitor a target even if it's automounted.

| Tip: The `libc` routine `realpath(3C)` is useful when you need to resolve a link into its ultimate target.

| Tip: Use `statvfs(2)` to recognize a remote file.

On the other hand, to avoid triggering and holding an automount, you can manually follow symbolic links until you reach either a local target, which you can then monitor, or a non-existent filesystem, in which case you might decide not to monitor the target. Another option is to test the target once to see if it is local, which triggers an automount only once if the target is automounted.

For example, the following routine determines if a given path is nonexistent, a dangling link, local, or remote.

    #include <errno.h>
    #include <limits.h>
    #include <stdlib.h>
    #include <sys/stat.h>
    #include <sys/statvfs.h>

    /*
     *  determine a file's location
     */

    enum location { ERROR, NONEXISTENT, DANGLING, LOCAL, REMOTE };

    enum location
    file_location(const char *path)
    {
         char target_path[PATH_MAX];
         struct stat statbuf;
         struct statvfs svfsbuf;

         if (!realpath(path, target_path))
         {
               /*
                *  realpath failed -- probably a permission
                *  problem, dangling link or nonexistent file.
                */
                if (errno == EACCES)
                    return ERROR;
                if (lstat(path, &statbuf) == 0)
                    return DANGLING;
                else if (errno == ENOENT)
                    return NONEXISTENT;
                else
                    return ERROR;
          }
         /*
          *  Realpath succeeded.  Find out if file is local.
          */
          if (statvfs(target_path, &svfsbuf) < 0)
              return ERROR;
          if (svfsbuf.f_flag & ST_LOCAL)
              return LOCAL;
          else
              return REMOTE;
    }

### FAM Examples

The following examples show event streams that FAM sends in certain situations.

Example: A client monitors an existing file. Later, another program appends data to the file. Even later, the client cancels the monitoring request.

    User calls     FAMMonitorFile(... “/a/b/c” ...)
    FAM events:    FAMExists       /a/b/c
                   FAMEndExist     /a/b/c

    Other program appends to file.
    FAM event:     FAMChanged      /a/b/c

    User calls     FAMCancelMonitor(...)
    FAM event:     FAMAcknowledge  /a/b/c

Example: A client monitors a directory containing two files. Later, another program creates a third file.

    User calls     FAMMonitorDirectory(... “/a/b” ...)
    FAM events:    FAMExists       /a/b
                   FAMExists       file_one
                   FAMExists       file_two
                   FAMEndExist     /a/b
    Third file created.
                   FAMCreated      file_three

Example: A client monitors an executable file which is already running. Later, the program exits.

    User calls     FAMMonitorFile(... “/a/b/program” ...)
    FAM events:    FAMExists       /a/b/program
                   FAMEndExist     /a/b/program
                   FAMStartExecuting /a/b/program
    Program exits.
    FAM event:     FAMStopExecuting /a/b/program

Example: A client makes an invalid request.

    User calls     FAMMonitorDirectory(... “relative/path” ...)
    FAM event:     FAMAcknowledge  relative/path

Example: A client monitors a nonexistent file. Later, another program creates the file.

    User calls     FAMMonitorFile(... “/a/b/c” ...)
    FAM events:    FAMDeleted      /a/b/c
                   FAMEndExist
    File is created.
    FAM event:     FAMCreated      /a/b/c

Example: A client monitors a directory containing some files. Another program deletes the directory, then creates a new file with the same name as the directory.

    User calls     FAMMonitorDirectory(... “/a/b” ...)
    FAM events:    FAMExists       /a/b
                   FAMExists       file_one
                   FAMExists       file_two
                   FAMEndExist     /a/b
    Directory and files are deleted.
    FAM events:    FAMDeleted      /a/b
                   FAMChanged      /a/b
                   FAMDeleted      file_one
                   FAMDeleted      file_two
    File with same name created.
    FAM events:    FAMCreated      /a/b
                   FAMChanged      /a/b

## Using FAM

As noted in “Detecting Changes to Files and Directories”, your application can check for changes in files in directories that it monitors in two ways:

- use `select()` to wait until the FAM socket is active, indicating a change, which is described in “Waiting for File Changes”.

- use `FAMPending()` to periodically poll FAM, which is explained in “Polling for File Changes”.

This section describes how to use both approaches.

### Waiting for File Changes

Follow these steps to use FAM in your application, using the select approach to detect changes:

1. Call `FAMOpen()` to create a connection to `fam`. This routine returns a FAMConnection structure used in all FAM procedures.

2. Call `FAMMonitorFile()` and `FAMMonitorDirectory()` to tell `fam` which files and directories to monitor.

3. Select on the `fam` socket file descriptor and call `FAMNextEvent()` when the `fam` socket is readable.

4. When the application is finished monitoring a file or directory, call `FAMCancelMonitor()`. If you want to temporarily suspend monitoring of a file or directory, call `FAMSuspendMonitor()`. When you're ready to start monitoring again, call `FAMResumeMonitor()`.

5. When the application no longer needs to monitor files and directories, call `FAMClose()` to release resources associated with files still being monitored and to close the connection to `fam`. This step is optional if you simply exit your application.

Example 8-1 demonstrates this process in a simple program.

Example 8-1. Using Select With FAM

    /*
     *   monitor.c -- monitor arbitrary file or directory
     *                using fam
     */

    #include <fam.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/stat.h>
    #include <sys/select.h>

    /* event_name() - return printable name of fam event code */

    const char *event_name(int code)
    {
        static const char *famevent[] = {
            "",
            "FAMChanged",
            "FAMDeleted",
            "FAMStartExecuting",
            "FAMStopExecuting",
            "FAMCreated",
            "FAMMoved",
            "FAMAcknowledge",
            "FAMExists",
            "FAMEndExist"
        };
        static char unknown_event[10];

        if (code < FAMChanged || code > FAMEndExist)
        {
            sprintf(unknown_event, "unknown (%d)", code);
            return unknown_event;
        }
        return famevent[code];
    }

    void main(int argc, char *argv[])
    {
        int i, nmon, rc, fam_fd;
        FAMConnection fc;
        FAMRequest *frp;
        struct stat status;
        FAMEvent fe;
        fd_set readfds;

        /* Allocate storage for requests */

        frp = malloc(argc * sizeof *frp);
        if (!frp)
        {
            perror("malloc");
            exit(1);
        }

        /* Open fam connection */

        if ((FAMOpen(&fc)) < 0)
        {
            perror("fam");
            exit(1);
        }

        /* Request monitoring for each program argument */

        for (nmon = 0, i = 1; i < argc; i++)
        {
            if (stat(argv[i], &status) < 0)
            {
                perror(argv[i]);
                status.st_mode = 0;
            }
            if ((status.st_mode & S_IFMT) == S_IFDIR)
                rc = FAMMonitorDirectory(&fc, argv[i], frp + i,
                                         NULL);
            else
                rc = FAMMonitorFile(&fc, argv[i], frp + i, NULL);
            if (rc < 0)
            {
                perror("FAMMonitor failed");
                continue;
            }
            nmon++;
        }
        if (!nmon)
        {
            fprintf(stderr, "Nothing monitored.\n");
            exit(1);
        }

        /* Initialize select data structure */

        fam_fd = FAMCONNECTION_GETfd(&fc);
        FD_ZERO(&readfds);
        FD_SET(fam_fd, &readfds);

        /* Loop forever. */

        while(1)
        {
            if (select(fam_fd + 1, &readfds,
                       NULL, NULL, NULL) < 0)
            {
                 perror("select failed");
                 exit(1);
            }
            if (FD_ISSET(fam_fd, &readfds))
            {
                if (FAMNextEvent(&fc, &fe) < 0)
                {
                    perror("FAMNextEvent");
                    exit(1);
                }
                printf("%-24s %s\n", fe.filename,
                       event_name(fe.code));
            }
        }
    }

### Polling for File Changes

Follow these steps to use FAM in your application, using the polling approach to detect changes:

1. Call `FAMOpen()` to create a connection to `fam`. This routine returns a FAMConnection structure used in all FAM procedures.

2. Call `FAMMonitorFile()` and `FAMMonitorDirectory()` to tell `fam` which files and directories to monitor.

3. Call `FAMPending()` to determine when there is a pending FAM event and then call `FAMNextEvent()` when an event is detected.

4. When the application is finished monitoring a file or directory, call `FAMCancelMonitor()`. If you want to temporarily suspend monitoring of a file or directory, call `FAMSuspendMonitor()`. When you're ready to start monitoring again, call `FAMResumeMonitor()`.

5. When the application no longer needs to monitor files and directories, call `FAMClose()` to free resources associated with files still being monitored and to close the connection to `fam`. This step is optional if you simply exit your application.

For example, you could use the polling approach in the `monitor.c` program listed in Example 8-1 by deleting the code pertaining to the select data structure and replacing the `while` loop with the code shown in Example 8-2, which demonstrates this process in a simple program.

Example 8-2. Polling With FAM

    while(1)
    {
        rc = FAMPending(&fc);
        if (rc == 0)
            break;
        else if (rc == -1)
            perror("FAMPending");
        if (FAMNextEvent(&fc, &fe) < 0)
        {
            perror("FAMNextEvent");
            exit(1);
        }
        printf("%-24s %s\n", fe.filename,
               event_name(fe.code));
    }

This is a particularly useful approach if you want to poll for changes from within an Xt work procedure. Example 8-3 shows the skeleton code for such a work procedure.

Example 8-3. Polling FAM Within an Xt Work Procedure

    Boolean monitorFiles(XtPointer clientData)
    {
        int rc = FAMPending(&fc);

        if (rc == 0)
            return(FALSE);
        else if (rc == -1)
            XtAppError(app_context, "FAMPending error");

        if (FAMNextEvent(&fc, &fe) < 0)
        {
            XtAppError(app_context, "FAMNextEvent error");
        }

        handleFileChange(fe);
        return(FALSE);
    }

Prev | Table of Contents | Next
Chapter 7. Interapplication Data Exchange |  | Chapter 9. Providing Online Help With SGIHelp