The io_close_ocb() function

Updated: October 28, 2024

This naturally brings us to the io_close_ocb() function. In most resource managers, you'd let the default library function, iofunc_close_ocb_default(), do the work. However, in our case, we may need to free a resource. Consider the case where a client performs the following perfectly legal (and useful for things like temporary files) code:

fp = fopen ("/ramdisk/tmpfile", "r+");
unlink ("/ramdisk/tmpfile");
// do some processing with the file
fclose (fp);

We cannot release the resources for the /ramdisk/tmpfile until after the link count (the number of open file descriptors to the file) goes to zero.

The fclose() will eventually translate within the C library into a close(), which will then trigger our RAM disk's io_close_ocb() handler. Only when the count goes to zero can we free the data.

Here's the code for the io_close_ocb():

int
cfs_io_close_ocb (resmgr_context_t *ctp, void *reserved,
                  RESMGR_OCB_T *ocb)
{
  cfs_attr_t    *attr;
  int           sts;

  attr = ocb -> attr;
  sts = iofunc_close_ocb (ctp, ocb, &attr -> attr);
  if (sts == EOK) {
    // release_attr makes sure that no one is using it...
    sts = release_attr (attr);
  }
  return (sts);
}

Note the attr -> attr — the helper function iofunc_close_ocb() expects the normal, nonextended attributes structure.

Once again, we rely on the services of release_attr() to ensure that the link count is zero.

Here's the source for release_attr() (from attr.c):

int
release_attr (cfs_attr_t *attr)
{
  int   i;

  // 1) check the count
  if (!attr -> attr.nlink  && !attr -> attr.count) {
    // decide what kind (file or dir) this entry is...

    if (S_ISDIR (attr -> attr.mode)) {
      // 2) it's a directory, see if it's empty
      if (attr -> nels > 2) {
        return (ENOTEMPTY);
      }
      // 3) need to free "." and ".."
      free (attr -> type.dirblocks [0].name);
      free (attr -> type.dirblocks [0].attr);
      free (attr -> type.dirblocks [1].name);
      free (attr -> type.dirblocks [1].attr);

      // 4) release the dirblocks[]
      if (attr -> type.dirblocks) {
        free (attr -> type.dirblocks);
        free (attr);
      }
    } else if (S_ISREG (attr -> attr.mode)) {
      // 5) a regular file
      for (i = 0; i < attr -> nels; i++) {
        cfs_block_free (attr,
                        attr -> type.fileblocks [i].iov_base);
        attr -> type.fileblocks [i].iov_base = NULL;
      }
      // 6) release the fileblocks[]
      if (attr -> type.fileblocks) {
        free (attr -> type.fileblocks);
        free (attr);
      }
    } else if (S_ISLNK (attr -> attr.mode)) {
      // 7) a symlink, delete the contents
      free (attr -> type.symlinkdata);
      free (attr);
    }
  }
  // 8) return EOK if everything went well
  return (EOK);
}

Note that the definition of “empty” is slightly different for a directory. A directory is considered empty if it has just the two entries . and .. within it.

You'll also note that we call free() to release all the objects. It's important that all the objects be allocated (whether via malloc()/calloc() for the dirblocks and fileblocks, or via stdrup() for the symlinkdata).

The code walkthrough is as follows:

  1. We verify the nlink count in the attributes structure, as well as the count maintained by the resource manager library. Only if both of these are zero do we go ahead and process the deletion.
  2. A directory is empty if it has exactly two entries (. and ..).
  3. We therefore free those two entries.
  4. Finally, we free the dirblocks array as well as the attributes structure (attr) itself.
  5. In the case of a file, we need to run through all of the fileblocks blocks and delete each one.
  6. Finally, we free the fileblocks array as well as the attributes structure itself.
  7. In the case of a symbolic link, we delete the content (the symlinkdata) and the attributes structure.
  8. Only if everything went well do we return EOK. It's important to examine the return code and discontinue further operations; for example, if we're trying to release a non-empty directory, you can't continue the higher-level function (in io_unlink(), for example) of releasing the parent's entry.