The c_unlink() function

Updated: October 28, 2024

To unlink an entry, the following code is used:

int
c_unlink (resmgr_context_t *ctp, io_unlink_t *msg,
          RESMGR_HANDLE_T *handle, void *reserved)
{
  des_t   parent, target;
  int     sts, sts2;
  struct  _client_info *cinfo;

  if ((sts = iofunc_client_info_ext (ctp, 0, &cinfo,
                IOFUNC_CLIENTINFO_GETGROUPS)) != EOK) {
    return (sts);
  }

  sts2 = connect_msg_to_attr (ctp, &msg -> connect, handle,
                              &parent, &target, &sts, cinfo);
  (void)iofunc_client_info_ext_free (&cinfo);
  if (sts2 != EOK) {
    return (sts);
  }

  if (sts != EOK) {
    return (sts);
  }

  // see below
  if (target.attr == handle) {
    return (EBUSY);
  }

  return (cfs_rmnod (&parent, target.name, target.attr));
}

The code implementing c_unlink() is straightforward as well—we get the client information and resolve the pathname. The destination had better exist, so if we don't get an EOK we return the error to the client. Also, it's a really bad idea (read: bug) to unlink the mount point, so we make a special check against the target attribute's being equal to the mount point attribute, and return EBUSY if that's the case. Note that QNX 4 returns the constant EBUSY, QNX Neutrino returns EPERM, and OpenBSD returns EISDIR. So, there are plenty of constants to choose from in the real world! I like EBUSY.

Other than that, the actual work is done in cfs_rmnod(), below.

int
cfs_rmnod (des_t *parent, char *name, cfs_attr_t *attr)
{
  int   sts;
  int   i;

  // 1) remove target
  attr -> attr.nlink--;
  if ((sts = release_attr (attr)) != EOK) {
    return (sts);
  }

  // 2) remove the directory entry out of the parent
  for (i = 0; i < parent -> attr -> nels; i++) {
    // 3) skip empty directory entries
    if (parent -> attr -> type.dirblocks [i].name == NULL) {
      continue;
    }
    if (!strcmp (parent -> attr -> type.dirblocks [i].name,
                 name)) {
      break;
    }
  }
  if (i == parent -> attr -> nels) {
    // huh.  gone.  This is either some kind of internal error,
    // or a race condition.
    return (ENOENT);
  }

  // 4) reclaim the space, and zero out the entry
  free (parent -> attr -> type.dirblocks [i].name);
  parent -> attr -> type.dirblocks [i].name = NULL;

  // 5) catch shrinkage at the tail end of the dirblocks[]
  while (parent -> attr -> type.dirblocks
         [parent -> attr -> nels - 1].name == NULL) {
    parent -> attr -> nels--;
  }

  // 6) could check the open count and do other reclamation
  //    magic here, but we don't *have to* for now...

  return (EOK);
}

Notice that we may not necessarily reclaim the space occupied by the resource! That's because the file could be in use by someone else. So the only time that it's appropriate to actually remove it is when the link count goes to zero, and that's checked for in the release_attr() routine as well as in the io_close_ocb() handler (below).

Here's the walkthrough:

  1. This is the place where we decrement the link count. The function release_attr() will try to remove the file, but will abort if the link count isn't zero, instead deferring the removal until io_close_ocb() decides it's safe to do so.
  2. The for loop scans the parent, attempting to find this directory entry by name.
  3. Notice that here we must skip removed entries, as mentioned earlier.
  4. Once we've found it (or errored-out), we free the space occupied by the strdup()'d name, and zero-out the dirblocks entry.
  5. We attempt to do a little bit of optimization by compressing empty entries at the end of the dirblocks array. This while loop will be stopped by .. which always exists.
  6. At this point, you could do further optimizations only if the directory entry isn't in use.