Managing Files and Directories
This section covers the system calls for renaming files, creating directories, and removing them. These are the building blocks behind the familiar mv, mkdir, and rmdir shell commands.
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
/* Returns 0 on success, -1 on error */
rename() just updates directory entries — no actual file data is moved. This makes it atomic and very fast even for large files.
| Situation | What Happens |
|---|---|
| newpath does not exist | oldpath is renamed to newpath |
| newpath exists (file) | newpath is removed, oldpath takes its place |
| newpath exists (dir) | Must be empty, otherwise ENOTEMPTY |
| oldpath == newpath | Nothing changes (success) |
| Different filesystems | EXDEV — rename cannot cross filesystems |
| oldpath is dir, newpath is file | ENOTDIR error |
| oldpath is symlink | The symlink is renamed (NOT dereferenced) |
old is a symlink, the symlink file itself is renamed.#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
/* Returns 0 on success, -1 on error */
Creates only the last component of pathname. If aaa/bbb does not exist and you call mkdir("aaa/bbb/ccc", 0755) it fails with ENOENT.
The new directory automatically gets two entries: . (itself) and .. (parent).
#include <unistd.h>
int rmdir(const char *pathname);
/* Returns 0 on success, -1 on error */
#include <stdio.h>
int remove(const char *pathname);
/* Returns 0 on success, -1 on error */
rmdir() removes an empty directory. Fails with ENOTEMPTY if the directory has any files or subdirectories (other than . and ..).
remove() is the universal version: it calls unlink() if pathname is a file, or rmdir() if it is a directory. It does NOT follow symlinks.
remove() when you don’t know if a path is a file or directory and want one call to handle both.Code Examples
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int atomic_write(const char *dest, const char *content) {
char tmppath[256];
snprintf(tmppath, sizeof(tmppath), "%s.tmp.XXXXXX", dest);
/* Use mkstemp to get a unique temp filename */
int fd = mkstemp(tmppath);
if (fd == -1) { perror("mkstemp"); return -1; }
write(fd, content, strlen(content));
close(fd);
/* Atomically replace dest with our new content */
if (rename(tmppath, dest) == -1) {
perror("rename");
unlink(tmppath);
return -1;
}
return 0;
}
int main() {
atomic_write("/tmp/config.cfg", "[server]\nport=8080\n");
printf("Config written atomically\n");
/* Update again */
atomic_write("/tmp/config.cfg", "[server]\nport=9090\ndebug=true\n");
printf("Config updated atomically\n");
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
int main() {
/* Setup */
mkdir("/tmp/dir_a", 0755);
mkdir("/tmp/dir_b", 0755);
/* Create a file in dir_a */
FILE *f = fopen("/tmp/dir_a/report.txt", "w");
fputs("Annual report data\n", f);
fclose(f);
/* Move AND rename in one call */
if (rename("/tmp/dir_a/report.txt", "/tmp/dir_b/report_2024.txt") == -1) {
perror("rename");
return 1;
}
printf("Moved and renamed successfully\n");
/* Verify old path is gone */
if (access("/tmp/dir_a/report.txt", F_OK) == -1)
printf("Old path no longer exists\n");
/* Cleanup */
unlink("/tmp/dir_b/report_2024.txt");
rmdir("/tmp/dir_a");
rmdir("/tmp/dir_b");
return 0;
}
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
int main() {
/* Create directory with rwxr-xr-x */
if (mkdir("/tmp/myapp_data", 0755) == -1) {
if (errno == EEXIST)
printf("Directory already exists\n");
else
perror("mkdir");
return 1;
}
printf("Created /tmp/myapp_data with mode 0755\n");
/* Create private directory — owner only */
if (mkdir("/tmp/myapp_private", 0700) == 0)
printf("Created /tmp/myapp_private with mode 0700 (owner only)\n");
/* Can't create multi-level in one call */
if (mkdir("/tmp/myapp_data/level1/level2", 0755) == -1)
printf("Failed (expected): %s\n", strerror(errno));
/* Cleanup */
rmdir("/tmp/myapp_data");
rmdir("/tmp/myapp_private");
return 0;
}
Output:
Created /tmp/myapp_data with mode 0755
Created /tmp/myapp_private with mode 0700 (owner only)
Failed (expected): No such file or directory
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
/* mkdir -p equivalent */
int mkdirp(const char *path, mode_t mode) {
char tmp[512];
char *p;
size_t len;
snprintf(tmp, sizeof(tmp), "%s", path);
len = strlen(tmp);
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
for (p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
mkdir(tmp, mode); /* ignore errors here */
*p = '/';
}
}
return mkdir(tmp, mode);
}
int main() {
if (mkdirp("/tmp/a/b/c/d", 0755) == -1 && errno != EEXIST) {
perror("mkdirp");
return 1;
}
printf("Created /tmp/a/b/c/d\n");
/* Cleanup */
rmdir("/tmp/a/b/c/d");
rmdir("/tmp/a/b/c");
rmdir("/tmp/a/b");
rmdir("/tmp/a");
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
int main() {
mkdir("/tmp/filled_dir", 0755);
/* Create a file inside */
int fd = open("/tmp/filled_dir/file.txt", O_CREAT | O_WRONLY, 0644);
close(fd);
/* Try to rmdir — should fail */
if (rmdir("/tmp/filled_dir") == -1)
printf("rmdir failed (expected): %s\n", strerror(errno));
/* Remove the file first, then rmdir */
unlink("/tmp/filled_dir/file.txt");
if (rmdir("/tmp/filled_dir") == 0)
printf("rmdir succeeded after emptying directory\n");
return 0;
}
Output:
rmdir failed (expected): Directory not empty
rmdir succeeded after emptying directory
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
int main() {
/* Create a file and a directory */
int fd = open("/tmp/removeme.txt", O_CREAT | O_WRONLY, 0644);
close(fd);
mkdir("/tmp/removedir", 0755);
/* remove() works on both */
if (remove("/tmp/removeme.txt") == 0)
printf("Removed file /tmp/removeme.txt\n");
if (remove("/tmp/removedir") == 0)
printf("Removed directory /tmp/removedir\n");
return 0;
}
Interview Questions
Yes, rename() is atomic on POSIX systems. It just updates directory entries in one kernel operation. Any reader sees either the old file or the new file, never a partial write. This makes it the standard way to safely update configuration files or logs.
If newpath is an existing file, it is silently removed and replaced by oldpath. If newpath is a non-empty directory, rename() fails with ENOTEMPTY. If newpath is an empty directory, it is removed and replaced.
No. rename() only manipulates directory entries within a single filesystem. Moving across filesystems requires copying the data and then deleting the original — which is what the mv command does internally when the source and destination are on different filesystems.
If you call mkdir(“a/b/c”, 0755), directories a and a/b must already exist. Only c is created. To create all missing levels, you need a loop similar to mkdir -p, calling mkdir() for each component.
rmdir() only works on empty directories. remove() works on both files (calls unlink()) and empty directories (calls rmdir()). Neither follows symlinks — they operate on the path itself.
Because rename() manipulates the directory entry itself, not the file the entry points to. If rename() followed symlinks, you couldn’t rename a symlink — you’d always be renaming its target, which is not the desired behavior.
