DEV Community

tyfkda
tyfkda

Posted on • Updated on

Implement `pwd` command on xv6

I just started playing with xv6, re-implementation of Unix version 6. It can easily build and run.

I thought implementing some command is a good practice, so I decided to add simple one, pwd. It can be made in pure user land, and no additional system call required.

Extract file information on xv6

Referring to ls.c, you can extract file information (struct stat) using stat function.

You can also enumerate directory entries calling open function with directory path, read contents as struct dirent.

Find absolute path

I am not familiar with concept of Unix, so looking for the code, and it seems struct inode (and its inum, inode number) is the key part for the file system. But interestingly, it doesn't have its file name. To get file name, you have to enumerate its directory and search for inum.

To get absolute path, you have to get file name recursively, until reach to the root.

Code

Here is the main function:

#include "types.h"
#include "fcntl.h"
#include "fs.h"
#include "stat.h"
#include "user.h"

#define NULL   ((void*)0)
#define FALSE  (0)
#define TRUE   (1)

#define PATH_SEPARATOR   "/"

static int getcwd(char* resultPath);
static char* goUp(int ino, char* ancestorPath, char* resultPath);
static int dirlookup(int fd, int ino, char* p);

int main(int argc, char *argv[]) {
  char resultPath[512];
  if (getcwd(resultPath))
    printf(1, "%s\n", resultPath);
  else
    printf(2, "pwd failed");
  exit();
}
Enter fullscreen mode Exit fullscreen mode

getcwd function returns current working directory to the given buffer:

static int getcwd(char* resultPath) {
  resultPath[0] = '\0';

  char ancestorPath[512];
  strcpy(ancestorPath, ".");

  struct stat st;
  if (stat(ancestorPath, &st) < 0)
    return FALSE;

  char* p = goUp(st.ino, ancestorPath, resultPath);
  if (p == NULL)
    return FALSE;
  if (resultPath[0] == '\0')
    strcpy(resultPath, PATH_SEPARATOR);
  return TRUE;
}
Enter fullscreen mode Exit fullscreen mode

ancestorPath holds relative path to ancestors. It becomes ".", "./..", "./../..", ... and is used to pass to stat function.

goUp function goes to parent directory for ancestorPath, and find the absolute path, recursively:

static char* goUp(int ino, char* ancestorPath, char* resultPath) {
  strcpy(ancestorPath + strlen(ancestorPath), PATH_SEPARATOR "..");
  struct stat st;
  if (stat(ancestorPath, &st) < 0)
    return NULL;

  if (st.ino == ino) {
    // No parent directory exists: must be the root.
    return resultPath;
  }

  char* foundPath = NULL;
  int fd = open(ancestorPath, O_RDONLY);
  if (fd >= 0) {
    char* p = goUp(st.ino, ancestorPath, resultPath);
    if (p != NULL) {
      strcpy(p, PATH_SEPARATOR);
      p += sizeof(PATH_SEPARATOR) - 1;

      // Find current directory.
      if (dirlookup(fd, ino, p))
        foundPath = p + strlen(p);
    }
    close(fd);
  }
  return foundPath;
}
Enter fullscreen mode Exit fullscreen mode

Even if you are at the root directory, you can get stat for the parent direcotry without error (and you can cd ..). It seems you have to detect you are at the root if parent's and current directory's ino are same.

dirlookup function finds file name for the given inode number from its parent directory:

// @param fd   file descriptor for a directory.
// @param ino  target inode number.
// @param p    [out] file name (part of absPath), overwritten by the file name of the ino.
static int dirlookup(int fd, int ino, char* p) {
  struct dirent de;
  while (read(fd, &de, sizeof(de)) == sizeof(de)) {
    if (de.inum == 0)
      continue;
    if (de.inum == ino) {
      memmove(p, de.name, DIRSIZ);
      p[DIRSIZ] = '\0';
      return TRUE;
    }
  }
  return FALSE;
}
Enter fullscreen mode Exit fullscreen mode

Build and add to file image

You can easily add your extra command to image file on xv6 . Modify UPROGS in Makefile with naming convention, add _pwd to it.

Test

$ pwd
/
$ mkdir foo
$ mkdir foo/bar
$ cd foo/bar
$ /pwd
/foo/bar
Enter fullscreen mode Exit fullscreen mode

I stumbled using pwd at /foo/bar. It seems xv6 doesn't have environment variables, and search path in a shell:

$ pwd  # <= without '/' causes exec error, because cannot find the command.
exec: fail
exec pwd failed
Enter fullscreen mode Exit fullscreen mode

You have to add / to point exact command path on xv6.

Top comments (0)