Chapter 18 Part 8 — Pathname Utilities | EmbeddedPathashala

 

symlink() · readlink() · realpath() · dirname() · basename()
Chapter 18 Part 8 — Pathname Utilities | EmbeddedPathashala
🔀
symlink()
📖
readlink()
🗺️
realpath()
✂️
dirname/basename

Pathname Utility Functions

This section covers functions for creating symlinks, reading the path inside a symlink, resolving a path to its absolute canonical form, and splitting a path into directory and filename parts.

symlink()readlink() realpath()dirname() basename()PATH_MAX EEXISTnull byte

symlink() — Create a Symbolic Link
#include <unistd.h>
int symlink(const char *filepath, const char *linkpath);
/* Returns 0 on success, -1 on error */

Creates a symlink file at linkpath whose content is the string filepath. The target filepath does NOT need to exist — you can create a dangling link. The target path can be absolute or relative.

If linkpath already exists, symlink() fails with EEXIST. To replace an existing link, unlink() it first.

readlink() — Read the Path Inside a Symlink
#include <unistd.h>
ssize_t readlink(const char *pathname, char *buffer, size_t bufsiz);
/* Returns: number of bytes written to buffer, or -1 on error */
⚠️ readlink() does NOT null-terminate the buffer! You must add ‘\0’ yourself at position [return_value].

To be safe, always allocate PATH_MAX bytes and null-terminate after the call:

char buf[PATH_MAX];
ssize_t n = readlink("/tmp/mylink", buf, sizeof(buf) - 1);
if (n != -1) buf[n] = '\0';

If the symlink content is longer than bufsiz, the string is truncated. There is no way to tell if truncation happened if the returned length exactly equals bufsiz — so always use PATH_MAX.

realpath() — Resolve a Pathname to Absolute Form
#include <stdlib.h>
char *realpath(const char *pathname, char *resolved_path);
/* Returns pointer to resolved path on success, NULL on error */

realpath() does everything in one step:

✅ Dereferences ALL symlinks in the path
✅ Resolves . (current dir) and .. (parent dir)
✅ Returns the absolute, canonical path

Example: realpath("../../etc/./passwd", buf)/etc/passwd

glibc extension: pass NULL as resolved_path and realpath() allocates the buffer for you. You must free() it.

dirname() and basename() — Split a Path
#include <libgen.h>
char *dirname(char *pathname);   /* Returns directory part */
char *basename(char *pathname);  /* Returns filename part */

Given /home/ravi/project/main.c:

dirname()
/home/ravi/project
Everything before the last /
basename()
main.c
The last component
⚠️ Both functions MAY MODIFY the input string. Always pass a copy (strdup) if you need the original later!

Input path dirname() basename()
/usr/bin/ls /usr/bin ls
/etc/passwd /etc passwd
passwd . passwd
/ / /
/etc/passwd//// /etc passwd
.. . ..

Code Examples

Example 1: Create, read, and remove a symlink
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>

int main() {
    /* Create the real file */
    FILE *f = fopen("/tmp/original.txt", "w");
    fputs("real content\n", f);
    fclose(f);

    /* Create symlink pointing to it */
    if (symlink("/tmp/original.txt", "/tmp/link.txt") == -1) {
        perror("symlink");
        return 1;
    }

    /* Read what the symlink points to */
    char buf[PATH_MAX];
    ssize_t n = readlink("/tmp/link.txt", buf, sizeof(buf) - 1);
    if (n == -1) { perror("readlink"); return 1; }
    buf[n] = '\0';   /* Must null-terminate manually */
    printf("Symlink points to: %s\n", buf);

    /* Access the file through the link */
    f = fopen("/tmp/link.txt", "r");
    fgets(buf, sizeof(buf), f);
    printf("Content via link: %s", buf);
    fclose(f);

    /* Cleanup */
    unlink("/tmp/link.txt");
    unlink("/tmp/original.txt");
    return 0;
}
Example 2: realpath() — resolve relative and complex paths
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

int main() {
    char resolved[PATH_MAX];

    /* Resolve a relative path */
    if (realpath("../../etc/hostname", resolved) == NULL)
        perror("realpath");
    else
        printf("Resolved: %s\n", resolved);

    /* Resolve a path with . and .. */
    if (realpath("/usr/bin/../lib/../bin/ls", resolved) != NULL)
        printf("Resolved: %s\n", resolved);

    /* Resolve the current directory */
    if (realpath(".", resolved) != NULL)
        printf("CWD resolved: %s\n", resolved);

    /* Using NULL buffer (glibc extension — remember to free!) */
    char *p = realpath("/etc/./passwd", NULL);
    if (p) {
        printf("Resolved (heap): %s\n", p);
        free(p);
    }

    return 0;
}
Example 3: dirname() and basename() — safe usage with strdup
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <string.h>

void print_parts(const char *path) {
    /* MUST use copies — dirname/basename may modify the string */
    char *copy1 = strdup(path);
    char *copy2 = strdup(path);

    printf("Path    : %s\n", path);
    printf("dirname : %s\n", dirname(copy1));
    printf("basename: %s\n", basename(copy2));
    printf("---\n");

    free(copy1);
    free(copy2);
}

int main() {
    print_parts("/home/ravi/projects/app/main.c");
    print_parts("/etc/passwd");
    print_parts("passwd");
    print_parts("/");
    print_parts("../data/file.txt");
    return 0;
}

Output:

Path    : /home/ravi/projects/app/main.c
dirname : /home/ravi/projects/app
basename: main.c
---
Path    : /etc/passwd
dirname : /etc
basename: passwd
---
Path    : passwd
dirname : .
basename: passwd
---
Path    : /
dirname : /
basename: /
---
Path    : ../data/file.txt
dirname : ../data
basename: file.txt
---
Example 4: Check if a path is a symlink and resolve it fully
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <stdlib.h>

void check_path(const char *path) {
    struct stat sb;

    if (lstat(path, &sb) == -1) {
        printf("%-30s: does not exist\n", path);
        return;
    }

    if (S_ISLNK(sb.st_mode)) {
        char link_target[PATH_MAX];
        ssize_t n = readlink(path, link_target, sizeof(link_target)-1);
        link_target[n] = '\0';

        char *resolved = realpath(path, NULL);
        printf("%-30s: symlink -> %s (resolves to: %s)\n",
               path, link_target, resolved ? resolved : "DANGLING");
        free(resolved);
    } else if (S_ISREG(sb.st_mode)) {
        printf("%-30s: regular file\n", path);
    } else if (S_ISDIR(sb.st_mode)) {
        printf("%-30s: directory\n", path);
    }
}

int main() {
    check_path("/bin");
    check_path("/etc/hostname");
    check_path("/tmp");
    return 0;
}
Example 5: Build an output path using dirname + custom suffix
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>

/* Given input "/src/utils.c", produce output "/build/utils.o" */
char *make_output_path(const char *input, const char *out_dir) {
    char *copy = strdup(input);
    char *base = basename(copy);

    /* Strip extension */
    char *dot = strrchr(base, '.');
    if (dot) *dot = '\0';

    char *result = malloc(strlen(out_dir) + strlen(base) + 8);
    sprintf(result, "%s/%s.o", out_dir, base);

    free(copy);
    return result;
}

int main() {
    const char *inputs[] = {
        "/home/ravi/project/src/main.c",
        "/home/ravi/project/src/utils.c",
        "/home/ravi/project/src/parser.c"
    };
    for (int i = 0; i < 3; i++) {
        char *out = make_output_path(inputs[i], "/tmp/build");
        printf("%s  ->  %s\n", inputs[i], out);
        free(out);
    }
    return 0;
}

Output:

/home/ravi/project/src/main.c  ->  /tmp/build/main.o
/home/ravi/project/src/utils.c  ->  /tmp/build/utils.o
/home/ravi/project/src/parser.c  ->  /tmp/build/parser.o

Interview Questions

Q1: What is the most common mistake when using readlink()?
Forgetting to null-terminate the buffer. readlink() does NOT add ‘\0’ at the end. You must write buf[n] = ‘\0’ after a successful call. Failing to do so causes undefined behavior when treating the buffer as a string.
Q2: What is the difference between readlink() and realpath()?
readlink() just returns the raw string stored inside the symlink (the link target). It doesn’t resolve the path or follow chains. realpath() fully resolves a pathname: follows all symlinks, resolves . and .., and returns the absolute canonical path of the final target.
Q3: Why must you pass copies to dirname() and basename()?
Both functions are permitted to modify their input argument (e.g., they may add or remove a trailing null byte). If you pass the original string, it may be corrupted. Always strdup() before calling.
Q4: What does dirname() return for input “filename” (no slash)?
It returns “.” (the current directory), because a relative filename with no directory prefix is in the current directory.
Q5: Can symlink() create a link to a non-existent target?
Yes. symlink() doesn’t verify that the target exists. The result is a dangling symlink. This is useful for creating links before the target is ready.
Q6: What does realpath() return if any component of the path doesn’t exist?
It returns NULL and sets errno to ENOENT. All components except the last must exist for realpath() to succeed.

Leave a Reply

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