What is the Working Directory?
Every process has a current working directory (CWD). When you use a relative path like ./file.txt or just file.txt, the kernel starts resolving it from the CWD. You can retrieve it with getcwd() and change it with chdir().
#include <unistd.h>
char *getcwd(char *cwdbuf, size_t size);
/* Returns pointer to cwdbuf on success, NULL on error */
Fills the buffer with the absolute pathname of the current working directory, null-terminated. Buffer should be at least PATH_MAX bytes (typically 4096).
#include <unistd.h>
int chdir(const char *pathname);
/* Change CWD to pathname (follows symlinks) */
#define _XOPEN_SOURCE 500
int fchdir(int fd);
/* Change CWD to the directory referred to by fd */
fchdir() is useful when you want to temporarily change to another directory and reliably return. You save the current directory as an fd first:
int saved = open(".", O_RDONLY); /* Save current dir */
chdir("/some/other/path"); /* Go elsewhere */
/* ... do work ... */
fchdir(saved); /* Return reliably */
close(saved);
#define _XOPEN_SOURCE 700
#include <fcntl.h>
int openat(int dirfd, const char *pathname, int flags, /* mode_t mode */);
Just like open(), but pathname is resolved relative to dirfd (an open directory file descriptor) instead of the CWD.
Other similar calls: faccessat(), fstatat(), mkdirat(), unlinkat(), renameat(), symlinkat(), readlinkat().
#define _BSD_SOURCE
#include <unistd.h>
int chroot(const char *pathname); /* Requires CAP_SYS_CHROOT */
Changes the process’s root directory. After chroot("/jail"), the process sees /jail as /. It cannot access files above /jail using absolute paths.
Code Examples
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
int main() {
char buf[PATH_MAX];
if (getcwd(buf, sizeof(buf)) == NULL) {
perror("getcwd");
return 1;
}
printf("Current working directory: %s\n", buf);
return 0;
}
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
int main() {
char buf[PATH_MAX];
/* Save current directory as a file descriptor */
int saved_dir = open(".", O_RDONLY | O_DIRECTORY);
if (saved_dir == -1) { perror("open ."); return 1; }
getcwd(buf, sizeof(buf));
printf("Starting in: %s\n", buf);
/* Change to /tmp */
if (chdir("/tmp") == -1) { perror("chdir"); return 1; }
getcwd(buf, sizeof(buf));
printf("Changed to : %s\n", buf);
/* Do some work in /tmp */
FILE *f = fopen("workfile.txt", "w");
fputs("temporary data", f);
fclose(f);
/* Return to original directory via fd — reliable! */
if (fchdir(saved_dir) == -1) { perror("fchdir"); return 1; }
close(saved_dir);
getcwd(buf, sizeof(buf));
printf("Returned to: %s\n", buf);
return 0;
}
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main() {
/* Open a base directory */
int dirfd = open("/tmp", O_RDONLY | O_DIRECTORY);
if (dirfd == -1) { perror("open dir"); return 1; }
/* Create a file inside that dir using openat() */
int fd = openat(dirfd, "myfile.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { perror("openat"); return 1; }
write(fd, "Hello from openat()\n", 20);
close(fd);
/* Read it back */
fd = openat(dirfd, "myfile.txt", O_RDONLY);
char buf[64];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("Read back: %s", buf);
close(fd);
/* Cleanup using unlinkat() */
unlinkat(dirfd, "myfile.txt", 0);
close(dirfd);
return 0;
}
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main() {
int dirfd = open("/etc", O_RDONLY | O_DIRECTORY);
struct stat sb;
/* Stat /etc/passwd relative to dirfd */
if (fstatat(dirfd, "passwd", &sb, 0) == -1) {
perror("fstatat");
return 1;
}
printf("/etc/passwd: size=%ld, inode=%lu\n",
(long)sb.st_size, (unsigned long)sb.st_ino);
/* Use AT_SYMLINK_NOFOLLOW to stat symlink itself */
if (fstatat(dirfd, "hostname", &sb, AT_SYMLINK_NOFOLLOW) == 0) {
printf("/etc/hostname: is symlink = %s\n",
S_ISLNK(sb.st_mode) ? "yes" : "no");
}
close(dirfd);
return 0;
}
#define _BSD_SOURCE
#include <stdio.h>
#include <unistd.h>
int main() {
/* This requires root privileges */
/* Set up a minimal jail at /tmp/jail */
/* (In practice you'd copy needed libraries too) */
if (chroot("/tmp/jail") == -1) {
perror("chroot (need root)");
return 1;
}
/* CRITICAL: always chdir to / after chroot */
if (chdir("/") == -1) {
perror("chdir");
return 1;
}
printf("Now jailed inside /tmp/jail\n");
printf("Can only access files under /tmp/jail\n");
/* Any absolute path like /etc/passwd now means /tmp/jail/etc/passwd */
FILE *f = fopen("/etc/passwd", "r");
if (!f)
printf("No /etc/passwd inside jail (expected)\n");
return 0;
}
Interview Questions
It is the directory from which relative pathnames (those not starting with /) are resolved. Every process has one, inherited from its parent. It can be changed with chdir() or fchdir().
fchdir() holds the directory open by its inode. Even if the directory is renamed or moved while you are away, fchdir() still returns you to the correct place. getcwd() stores a path string that may become invalid if the directory is renamed.
openat() opens a file relative to an open directory fd rather than the CWD. It avoids TOCTOU race conditions in multi-threaded code (because another thread might chdir() between your check and your open). It also lets threads maintain independent “virtual” working directories.
It is a special constant that means “use the current working directory”. openat(AT_FDCWD, “file.txt”, flags) is exactly equivalent to open(“file.txt”, flags).
chroot() changes the process’s root directory so it cannot access the real filesystem above that point. Limitations: (1) must chdir(“/”) after chroot or CWD escapes the jail, (2) a privileged process can break out by creating device files or receiving fds via sockets, (3) not suitable as a security boundary for privileged programs.
Either
#define _XOPEN_SOURCE 700 or #define _POSIX_C_SOURCE 200809L before including fcntl.h.