The redirect_symlink() function

How to redirect a symbolic link is an interesting topic.

First of all, there are two cases to consider: either the symlink points to an absolute pathname (one that starts with a leading / character) or it doesn't and hence is relative.

For the absolute pathname, we need to forget about the current path leading up to the symbolic link, and replace the entire path up to and including the symbolic link with the contents of the symbolic link:

ln -s /tmp /ramdisk/tempfiles

In that case, when we resolve /ramdisk/tempfiles, we will redirect the symlink to /tmp. However, in the relative case:

ln -s ../resume.html resume.htm

When we resolve the relative symlink, we need to preserve the existing pathname up to the symlink, and replace only the symlink with its contents. So, in our example above, if the path was /ramdisk/old/resume.htm, we would replace the symlink, resume.htm, with its contents, ../resume.html, to get the pathname /ramdisk/old/../resume.html as the redirection result. Someone else is responsible for resolving /ramdisk/old/../resume.html into /ramdisk/resume.html.

In both cases, we preserve the contents (if any) after the symlink, and simply append that to the substituted value.

Here is the redirect_symlink() function presented with comments so that you can see what's going on:

static void
redirect_symlink (resmgr_context_t *ctp,
                  struct _io_connect *msg, cfs_attr_t *attr,
                  des_t *components, int ncomponents)
{
  int   eflag;
  int   ftype;
  char  newpath [PATH_MAX];
  int   i;
  char  *p;
  struct _io_connect_link_reply     link_reply;

  // 1) set up variables
  i = 1;
  p = newpath;
  *p = 0;

  // 2) a relative path, do up to the symlink itself
  if (*attr -> type.symlinkdata != '/') {
    // 3) relative -- copy up to and including
    for (; i < (ncomponents - 1); i++) {
      strcat (p, components [i].name);
      p += strlen (p);
      strcat (p, "/");
      p++;
    }
  } else {
    // 4) absolute, discard up to and including
    i = ncomponents - 1;
  }

  // 5) now substitute the content of the symlink
  strcat (p, attr -> type.symlinkdata);
  p += strlen (p);

  // skip the symlink itself now that we've substituted it
  i++;

  // 6) copy the rest of the pathname components, if any
  for (; components [i].name && i < PATH_MAX; i++) {
    strcat (p, "/");
    strcat (p, components [i].name);
    p += strlen (p);
  }

  // 7) preserve these, wipe rest
  eflag = msg -> eflag;
  ftype = msg -> file_type;
  memset (&link_reply, 0, sizeof (link_reply));

  // 8) set up the reply
  _IO_SET_CONNECT_RET (ctp, _IO_CONNECT_RET_LINK);
  link_reply.file_type = ftype;
  link_reply.eflag = eflag;
  link_reply.path_len = strlen (newpath) + 1;
  SETIOV (&ctp -> iov [0], &link_reply, sizeof (link_reply));
  SETIOV (&ctp -> iov [1], newpath, link_reply.path_len);

  MsgReplyv (ctp -> rcvid, ctp -> status, ctp -> iov, 2);
}
  1. The architecture of the RAM-disk resource manager is such that by the time we're called to fix up the path for the symlink, we have the path already broken up into components. Therefore, we use the variable newpath (and the pointer p) during the reconstruction phase.
  2. The variable ncomponents tells us how many components were processed before connect_msg_to_attr() stopped processing components (in this case, because it hit a symlink). Therefore, ncomponents - 1 is the index of the symlink entry. We see if the symlink is absolute (begins with /) or relative.
  3. In the relative case, we need to copy (because we are reconstructing components into newpath) all of the components up to but not including the symbolic link.
  4. In the absolute case, we discard all components up to and including the symbolic link.
  5. We then copy the contents of the symlink in place of the symlink, and increment i (our index into the original pathname component array).
  6. Then we copy the rest of the pathname components, if any, to the end of the new path string that we're constructing.
  7. While preparing the reply buffer, we need to preserve the eflag and file_type members, so we stick them into local variables. Then we clear out the reply buffer via memset().
  8. The reply consists of setting a flag via the macro _IO_SET_CONNECT_RET() (to indicate that this is a redirection, rather than a pass/fail indication for the client's open()), restoring the two flags we needed to preserve, setting the path_len parameter to the length of the string that we are returning, and setting up a two part IOV for the return. The first part of the IOV points to the struct _io_connect_link_reply (the header), the second part of the reply points to the string (in our case, newpath). Finally, we reply via MsgReplyv().

So basically, the main trick was in performing the symlink substitution, and setting the flag to indicate redirection.