Chapter 18 Part 4 — Directory Management System Calls | EmbeddedPathashala

 

rename() · mkdir() · rmdir() · remove()
Chapter 18 Part 4 — Directory Management System Calls | EmbeddedPathashala
✏️
rename()
📁
mkdir()
🗑️
rmdir()
🧹
remove()

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.

rename()mkdir() rmdir()remove() EXDEVENOTEMPTY S_ISGIDumask

rename() — Move or Rename a File
#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)
📌 rename() does NOT follow symlinks in either argument. If old is a symlink, the symlink file itself is renamed.
📌 rename() is the only way to do atomic file updates: write to a temp file, then rename over the target. Readers either see old or new, never a partial write.

mkdir() — Create a New Directory
#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).

Parent dir
mode: 2755 (set-GID bit)
→ mkdir() →
New subdir
inherits set-GID bit!
new files inherit group
⚠️ The set-user-ID bit (S_ISUID) is always cleared on new directories — it has no meaning for directories.

rmdir() and remove()
#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.

✅ Use remove() when you don’t know if a path is a file or directory and want one call to handle both.

Code Examples

Example 1: rename() for atomic file update
#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;
}
Example 2: rename() — move a file to another directory
#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;
}
Example 3: mkdir() with permission bits
#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
Example 4: Recursive mkdir — create all levels
#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;
}
Example 5: rmdir() — fails if not empty
#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
Example 6: remove() — works on both files and directories
#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

Q1: Is rename() atomic? Why is it used for safe file updates?
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.
Q2: What happens if newpath already exists in rename()?
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.
Q3: Can rename() move a file across filesystems?
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.
Q4: mkdir() creates only the last component. What does this mean?
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.
Q5: What is the difference between rmdir() and remove()?
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.
Q6: Why does rename() not follow symlinks?
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.

Leave a Reply

Your email address will not be published. Required fields are marked *