The pathwalk() function

The pathwalk() function is called only by connect_msg_to_attr() and by the rename function (c_rename(), which we'll see later). Let's look at this lowest-level function first, and then we'll proceed up the call hierarchy.

int
pathwalk (resmgr_context_t *ctp, char *pathname,
          cfs_attr_t *mountpoint, int flags, des_t *output,
          int *nrets, struct _client_info *cinfo)
{
  int           nels;
  int           sts;
  char          *p;

  // 1) first, we break apart the slash-separated pathname
  memset (output, 0, sizeof (output [0]) * *nrets);
  output [0].attr = mountpoint;
  output [0].name = "";

  nels = 1;
  for (p = strtok (pathname, "/"); p; p = strtok (NULL, "/")) {
    if (nels >= *nrets) {
      return (E2BIG);
    }
    output [nels].name = p;
    output [nels].attr = NULL;
    nels++
  }

  // 2) next, we analyze each pathname
  for (*nrets = 1; *nrets < nels; ++*nrets) {

    // 3) only directories can have children.
    if (!S_ISDIR (output [*nrets - 1].attr -> attr.mode)) {
      return (ENOTDIR);
    }

    // 4) check access permissions
    sts = iofunc_check_access (ctp,
                               &output [*nrets-1].attr -> attr,
                               S_IEXEC, cinfo);
    if (sts != EOK) {
      return (sts);
    }

    // 5) search for the entry
    output [*nrets].attr = search_dir (output [*nrets].name,
                                       output [*nrets-1].attr);
    if (!output [*nrets].attr) {
      ++*nrets;
      return (ENOENT);
    }

    // 6) process the entry
    if (S_ISLNK (output [*nrets].attr -> attr.mode)) {
      ++*nrets;
      return (EOK);
    }
  }

  // 7) everything was okay
  return (EOK);
}

The pathwalk() function fills the output parameter with the pathnames and attributes structures of each pathname component. The *nrets parameter is used as both an input and an output. In the input case it tells pathwalk() how big the output array is, and when pathwalk() returns, *nrets is used to indicate how many elements were successfully processed (see the walkthrough below). Note that the way that we've broken the string into pieces first, and then processed the individual components one at a time means that when we abort the function (for any of a number of reasons as described in the walkthrough), the output array may have elements that are valid past where the *nrets variable indicates. This is actually useful; for example, it lets us get the pathname of a file or directory that we're creating (and hence doesn't exist). It also lets us check if there are additional components past the one that we're creating, which would be an error.

Detailed walkthrough:

  1. The first element of the output string is, by definition, the attributes structure corresponding to the mount point and to the empty string. The for loop breaks the pathname string apart at each and every / character, and checks to see that there aren't too many of them (the caller tells us how many they have room for in the passed parameter *nrets).
    Note: Note that we use strtok() which isn't thread-safe; in this resource manager we are single-threaded. We would have used strtok_r() if thread-safety were a concern.
  2. Next, we enter a for loop that analyzes each pathname component. It's within this loop that we do all of the checking. Note that the variable *nrets points to the "current" pathname element.
  3. In order for the current pathname element to even be valid, its parent (i.e. *nrets minus 1) must be a directory, since only directories can have children. If that isn't the case, we return ENOTDIR and abort. Note that when we abort, the *nrets return value includes the nondirectory that failed.
  4. We use the helper function iofunc_check_access() to verify accessibility for the component. Note that if we abort, *nrets includes the inaccessible directory.
  5. At this point, we have verified that everything is okay up to the entry, and all we need to do is find the entry within the directory. The helper function search_dir() looks through the dirblocks array member of the extended attributes structure and tries to find our entry. If the entry isn't found, *nrets includes the entry. (This is important to make note of when creating files or directories that don't yet exist!)
  6. We check if the entry itself is a symbolic link. If it is, we give up, and let higher levels of software deal with it. We return EOK because there's nothing actually wrong with being a symbolic link, it's just that we can't do anything about it at this level. (Why? The symbolic link could be a link to a completely different filesystem that we have no knowledge of.) The higher levels of software will eventually tell the client that the entry is a symlink, and the client's library then tries the path again — that's why we don't worry about infinite symlink loops and other stuff in our resource manager. The *nrets return value includes the entry.
  7. Finally, if everything works, (we've gone through all of the entries and found and verified each and every one of them) we return EOK and *nrets contains all pathname elements.

The job of *nrets is to give the higher-level routines an indication of where the processing stopped. The return value from pathwalk() will tell them why it stopped.