ext2 Extended File Attributes and ioctl() in Linux: Complete Guide

 

ext2 Extended File Attributes and ioctl() in Linux: Complete Guide
ext2 Extended File Attributes and ioctl() in Linux: Complete Guide
12
Flag Types
ioctl()
System Call
chattr
Shell Tool

What are I-node Flags?

Beyond the standard 12 permission bits, Linux allows you to set special i-node flags on files and directories. These are additional attributes that control how the kernel treats the file — things like making it immutable, enabling append-only mode, or skipping atime updates.

Originally an ext2 feature, i-node flags are now supported on ext3, ext4, XFS, Btrfs, JFS, and Reiserfs. They are a Linux-specific extension (not in POSIX), accessed via the ioctl() system call from programs, or via the chattr and lsattr shell commands.

Key Terms:

i-node flags ioctl() FS_IOC_GETFLAGS FS_IOC_SETFLAGS FS_IMMUTABLE_FL FS_APPEND_FL FS_NOATIME_FL chattr lsattr linux/fs.h

1. Complete I-node Flag Reference
Constant (linux/fs.h) chattr Purpose Implemented?
FS_IMMUTABLE_FL i File data and metadata cannot be changed. Even root cannot modify it. ✓ Yes
FS_APPEND_FL a File can only be opened with O_APPEND. Data can only be added, never overwritten. ✓ Yes
FS_NOATIME_FL A Skip atime updates on access. Improves I/O performance for read-heavy files. ✓ Yes
FS_SYNC_FL S Writes are synchronous (like O_SYNC). Data goes to disk immediately. ✓ Yes
FS_DIRSYNC_FL D Directory updates are synchronous. Only for directories. ✓ Yes (2.6+)
FS_JOURNAL_DATA_FL j Journal file data as well as metadata. ext3/ext4 only. ✓ Yes
FS_NODUMP_FL d Exclude from dump(8) backups. ✓ Yes
FS_NOTAIL_FL t Disable tail packing. Reiserfs only. ✓ Yes
FS_TOPDIR_FL T Mark as top-level directory for Orlov block allocator. ext2/3/4. ✓ Yes (2.6+)
FS_COMPR_FL c Compress file on disk. (API exists but not in any major fs.) ✗ Not implemented
FS_SECRM_FL s Securely wipe file on delete. (Not implemented.) ✗ Not implemented
FS_UNRM_FL u Allow undelete. (Not implemented.) ✗ Not implemented
Most important for interviews: Know FS_IMMUTABLE_FL (i), FS_APPEND_FL (a), and FS_NOATIME_FL (A). These are real-world relevant.

2. Shell Usage — chattr and lsattr

The easiest way to work with i-node flags is via shell commands:

# View current flags on a file
$ lsattr myfile
--------------e---- myfile     (e = uses extents, default on ext4)

# Set a flag using chattr (+flag adds, -flag removes, =flag sets exactly)
$ chattr +i myfile             # Make immutable
$ lsattr myfile
----i---------e---- myfile

# Try to delete it — even root fails!
$ rm myfile
rm: cannot remove 'myfile': Operation not permitted

# Remove immutable flag first
$ chattr -i myfile
$ rm myfile                    # Now it works

# Set append-only (useful for log files)
$ chattr +a /var/log/myapp.log
# Now: open(log, O_WRONLY) fails, but open(log, O_WRONLY|O_APPEND) succeeds

# Set both append-only and immutable
$ chattr +ai myfile

# Skip atime updates for performance
$ chattr +A myfile

# Apply flags recursively to a directory tree
$ chattr -R +A /var/www/html   # Skip atime on all web files

3. Code Examples — Using ioctl() to Get/Set Flags

From C programs, use ioctl() with FS_IOC_GETFLAGS and FS_IOC_SETFLAGS:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fs.h>   /* FS_IOC_GETFLAGS, FS_IOC_SETFLAGS, FS_*_FL */
#include <stdlib.h>

Example 1: Read current i-node flags

int get_inode_flags(const char *path) {
    int fd, flags;

    fd = open(path, O_RDONLY);
    if (fd == -1) { perror("open"); return -1; }

    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
        perror("ioctl GETFLAGS");
        close(fd);
        return -1;
    }
    close(fd);
    return flags;
}

int main(void) {
    int flags = get_inode_flags("myfile");
    if (flags == -1) return 1;

    printf("Flags: 0x%x\n", flags);
    if (flags & FS_IMMUTABLE_FL)    printf("  Immutable (i)\n");
    if (flags & FS_APPEND_FL)       printf("  Append-only (a)\n");
    if (flags & FS_NOATIME_FL)      printf("  No-atime (A)\n");
    if (flags & FS_SYNC_FL)         printf("  Sync (S)\n");
    if (flags & FS_NODUMP_FL)       printf("  No-dump (d)\n");
    if (flags & FS_JOURNAL_DATA_FL) printf("  Journal data (j)\n");

    return 0;
}

Example 2: Set the FS_NOATIME_FL flag (skip atime updates)

int set_noatime_flag(const char *path) {
    int fd, flags;

    /* Must open with write access to set flags (or be owner/root) */
    fd = open(path, O_RDONLY);
    if (fd == -1) { perror("open"); return -1; }

    /* Step 1: Get current flags */
    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
        perror("ioctl GETFLAGS");
        close(fd);
        return -1;
    }

    /* Step 2: Add the flag using OR */
    flags |= FS_NOATIME_FL;

    /* Step 3: Set the updated flags */
    if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
        perror("ioctl SETFLAGS");
        close(fd);
        return -1;
    }

    printf("FS_NOATIME_FL set on %s\n", path);
    close(fd);
    return 0;
}

int main(void) {
    return set_noatime_flag("myfile");
}

Example 3: Clear (remove) a specific flag

int clear_flag(const char *path, int flag_to_clear) {
    int fd, flags;

    fd = open(path, O_RDONLY);
    if (fd == -1) { perror("open"); return -1; }

    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
        close(fd); return -1;
    }

    /* Remove the flag using AND NOT */
    flags &= ~flag_to_clear;

    if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
        close(fd); return -1;
    }

    close(fd);
    return 0;
}

int main(void) {
    /* Remove noatime flag */
    if (clear_flag("myfile", FS_NOATIME_FL) == 0)
        printf("Cleared FS_NOATIME_FL\n");
    return 0;
}

Example 4: Make a file immutable (needs CAP_LINUX_IMMUTABLE / root)

/* WARNING: Run as root. Once immutable, even root cannot modify/delete.
   Must clear immutable flag first to make any changes. */

int make_immutable(const char *path) {
    int fd, flags;

    fd = open(path, O_RDONLY);
    if (fd == -1) { perror("open"); return -1; }

    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
        close(fd); return -1;
    }

    flags |= FS_IMMUTABLE_FL;

    if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
        perror("ioctl SETFLAGS (need root and CAP_LINUX_IMMUTABLE)");
        close(fd);
        return -1;
    }

    printf("File is now immutable. Cannot be changed or deleted.\n");
    close(fd);
    return 0;
}

/* To reverse it: clear_flag(path, FS_IMMUTABLE_FL)
   Use case: protect critical system config files from accidental deletion
   e.g. make_immutable("/etc/hosts");  */

Example 5: Set append-only on a log file

/* Append-only: processes can only add data, never overwrite.
   Good for log files — even if program is compromised, it can't
   falsify old log entries. */

int make_append_only(const char *path) {
    int fd, flags;

    fd = open(path, O_RDONLY);
    if (fd == -1) { perror("open"); return -1; }

    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
        close(fd); return -1;
    }

    flags |= FS_APPEND_FL;

    if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
        perror("ioctl (need root)");
        close(fd); return -1;
    }

    printf("Log file is now append-only\n");
    close(fd);
    return 0;
}

/* After this:
   open(path, O_WRONLY)         → EPERM
   open(path, O_WRONLY|O_APPEND) → OK
   write(fd, ...)               → data appended to end only */

Example 6: Utility — print all set flags as a string

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>

struct { int flag; char letter; } flag_map[] = {
    { FS_APPEND_FL,      'a' },
    { FS_NODUMP_FL,      'd' },
    { FS_IMMUTABLE_FL,   'i' },
    { FS_JOURNAL_DATA_FL,'j' },
    { FS_NOATIME_FL,     'A' },
    { FS_SYNC_FL,        'S' },
    { FS_DIRSYNC_FL,     'D' },
    { FS_NOTAIL_FL,      't' },
    { FS_TOPDIR_FL,      'T' },
    { 0, 0 }
};

void lsattr_for_file(const char *path) {
    int fd, flags, i;
    char flag_str[20] = "-------------";  /* 13 dashes = no flags */

    fd = open(path, O_RDONLY);
    if (fd == -1) { perror("open"); return; }

    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == 0) {
        for (i = 0; flag_map[i].flag; i++) {
            if (flags & flag_map[i].flag)
                flag_str[i] = flag_map[i].letter;
        }
        printf("%s  %s\n", flag_str, path);
    }
    close(fd);
}

int main(int argc, char *argv[]) {
    if (argc < 2) { fprintf(stderr, "Usage: %s file...\n", argv[0]); return 1; }
    for (int i = 1; i < argc; i++)
        lsattr_for_file(argv[i]);
    return 0;
}

4. Flag Inheritance — Directories and Their Contents

When you set i-node flags on a directory, those flags are generally inherited by new files and subdirectories created inside. But there are exceptions:

Flag on Directory Inherited by new files? Inherited by new subdirs?
Most flags (A, S, etc.) Yes Yes
FS_DIRSYNC_FL (D) No (files only) Yes (subdirs only)
FS_IMMUTABLE_FL (i) Not inherited Not inherited
FS_IMMUTABLE_FL is not inherited because an immutable directory can’t have new entries added to it — so inheritance is impossible.

5. Which Filesystems Support I-node Flags?
Filesystem I-node flags support Notes
ext2 ✓ Origin Original home of this feature
ext3 ✓ Full Adds FS_JOURNAL_DATA_FL support
ext4 ✓ Full Most commonly used today
Btrfs ✓ Partial Immutable, noatime, and others
XFS ✓ Partial Since Linux 2.4.25
Reiserfs ✓ Partial Needs -o attrs mount option
JFS ✓ Partial Since Linux 2.6.17
FAT / NTFS ✗ Not supported No i-node concept

6. Interview Questions

Q1 What system call is used to get and set i-node flags from a C program?

A ioctl() with the operations FS_IOC_GETFLAGS (to read flags into an int) and FS_IOC_SETFLAGS (to write flags). Requires an open file descriptor and the headers <sys/ioctl.h> and <linux/fs.h>.

Q2 What is the difference between the immutable flag and the append-only flag?

A Immutable (FS_IMMUTABLE_FL) prevents all changes — no writes, no chmod, no chown, no rename, no delete. Even root is blocked. Append-only (FS_APPEND_FL) allows data to be added at the end (with O_APPEND), but existing data cannot be overwritten and the file cannot be deleted.

Q3 What privilege is needed to set the immutable or append-only flag?

A The CAP_LINUX_IMMUTABLE capability (which root has by default). Normal unprivileged users cannot set these flags.

Q4 What is the practical use case for FS_NOATIME_FL?

A Disabling atime updates avoids a disk write on every file read. For read-heavy workloads (web servers serving static files, database systems, mail servers), this significantly reduces disk I/O. It’s equivalent to the noatime mount option but per-file.

Q5 Shell commands: how do you make a file append-only and verify it?

A

$ chattr +a logfile.log    # Set append-only
$ lsattr logfile.log       # Verify: shows 'a' in flag string
# -----a--------e---- logfile.log
$ echo "new line" >> logfile.log    # OK (append)
$ echo "overwrite" > logfile.log    # FAILS: -bash: logfile.log: Operation not permitted

Q6 Are i-node flags part of POSIX?

A No. I-node flags are a Linux-specific (non-standard) extension, originally from ext2. They are accessed via ioctl() rather than a dedicated POSIX system call. BSDs have a similar feature called “file flags” via chflags(), but the API is different.

Q7 Write the correct ioctl call pattern to enable FS_SYNC_FL on a file.

A

#include <sys/ioctl.h>
#include <linux/fs.h>
#include <fcntl.h>

int attr;
int fd = open("myfile", O_RDONLY);
ioctl(fd, FS_IOC_GETFLAGS, &attr);  /* get current flags */
attr |= FS_SYNC_FL;                  /* add the flag */
ioctl(fd, FS_IOC_SETFLAGS, &attr);  /* write back */
close(fd);

Chapter 15 Summary — All Topics at a Glance

You have completed Chapter 15 — File Attributes!

← Previous Back to Start

Leave a Reply

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