Implement `pwd` command on xv6
Tags: xv6, c
Published: 2018-06-07 14:19:26

---
title: Implement `pwd` command on xv6
published: true
description: 
tags: xv6, c
---

I just started playing with [xv6](https://github.com/mit-pdos/xv6-public), 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](https://github.com/mit-pdos/xv6-public/blob/master/ls.c), you can extract file information ([struct `stat`](https://github.com/mit-pdos/xv6-public/blob/master/stat.h#L5)) using [`stat`](https://github.com/mit-pdos/xv6-public/blob/master/ulib.c#L71) function.

You can also enumerate directory entries calling [`open`](https://github.com/mit-pdos/xv6-public/blob/master/user.h#L14) function with directory path, [`read`](https://github.com/mit-pdos/xv6-public/blob/master/user.h#L10) contents as [struct `dirent`](https://github.com/mit-pdos/xv6-public/blob/master/fs.h#L53).

### Find absolute path

I am not familiar with concept of Unix, so looking for the code, and it seems [struct `inode`](https://github.com/mit-pdos/xv6-public/blob/master/file.h#L13) (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:

```c
#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();
}
```

`getcwd` function returns current working directory to the given buffer:

```c
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;
}
```

`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:

```c
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;
}
```

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:

```c
// @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;
}
```

### Build and add to file image

You can easily add your extra command to image file on xv6 . Modify [UPROGS in Makefile](https://github.com/mit-pdos/xv6-public/blob/master/Makefile#L161) with naming convention, add `_pwd` to it.

### Test

```sh
$ pwd
/
$ mkdir foo
$ mkdir foo/bar
$ cd foo/bar
$ /pwd
/foo/bar
```

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

```sh
$ pwd  # <= without '/' causes exec error, because cannot find the command.
exec: fail
exec pwd failed
```

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