The c_unlink() function
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:
- 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.
- The
forloop scans the parent, attempting to find this directory entry by name. - Notice that here we must skip removed entries, as mentioned earlier.
- Once we've found it (or errored-out), we free the space occupied by the strdup()'d name, and zero-out the dirblocks entry.
- We attempt to do a little bit of optimization by compressing empty entries at the end of the
dirblocks array.
This
whileloop will be stopped by .. which always exists. - At this point, you could do further optimizations only if the directory entry isn't in use.
