Linux Restrictions, Limits and File System Specifics Explained
Max name length
Max value size (VFS)
Total limit on ext*
Total limit on JFS
Key Terms
Not all file types can carry user EAs. Not all file systems give you the same amount of storage. The Linux VFS layer defines the maximum possible limits, and individual file systems can be more restrictive. Understanding these limits prevents surprising errors at runtime.
User EAs can only be placed on regular files and directories. All other file types are excluded.
| File Type | User EA Allowed? | Reason if Not Allowed |
|---|---|---|
Regular file (-rw-r--r--) |
✓ | Normal permissions apply |
Directory (drwxr-xr-x) |
✓ * | Allowed, but sticky bit restriction applies |
Symbolic link (lrwxrwxrwx) |
✗ | Permissions are always 777 and can’t be changed — can’t use permissions to control EA access |
Device file (crw-, brw-) |
✗ | Permissions control I/O access — using them for EAs would conflict |
Socket (s---) |
✗ | Same reason as device file — permissions serve I/O purpose |
FIFO / Named pipe (p---) |
✗ | Same reason as device file |
On Linux, symbolic links always have permissions lrwxrwxrwx (0777). These permissions have no enforcement meaning — the kernel ignores them. The actual permission check happens on the target file when the symlink is dereferenced.
The problem: EA access control depends on file permissions. If a symlink’s permissions can’t be restricted, then any user could set user EAs on any symlink — there’s no way to say “only the owner can modify this symlink’s EAs”.
The kernel’s solution: Simply ban user EAs on symlinks entirely.
/* This will fail with EPERM (operation not permitted) */
#include <sys/xattr.h>
#include <stdio.h>
#include <string.h>
int main() {
/* Create a symlink first */
symlink("/etc/passwd", "/tmp/my_symlink");
/* Try to set a user EA on the symlink (NOT the target) */
const char *val = "test";
int ret = lsetxattr("/tmp/my_symlink", "user.tag", val, strlen(val), 0);
if (ret == -1) {
perror("lsetxattr on symlink");
/* Output: lsetxattr on symlink: Operation not permitted */
}
return 0;
}
lsetxattr() is the variant that does NOT dereference symlinks. setxattr() would follow the symlink and set the EA on the target file instead.Even though directories support user EAs, there is a special restriction: an unprivileged process cannot set user EAs on a directory owned by another user if the sticky bit is set on that directory.
| Directory ownership & sticky bit scenarios | ||
| Scenario A — Allowed
Directory owner: ✓ Alice can set user EAs |
Scenario B — Allowed
Directory owner: ✓ Alice owns it — allowed |
Scenario C — Blocked
Directory owner: ✗ EPERM — blocked |
The sticky bit on a directory (like /tmp) means “users can’t delete other users’ files here”. The same logic extends to EAs: you can’t modify another user’s EA in a sticky directory.
# /tmp typically has the sticky bit set (shown as 't' in permissions)
$ ls -ld /tmp
drwxrwxrwt 20 root root 4096 Jan 10 09:00 /tmp
# Alice tries to set a user EA on a file owned by ravi inside /tmp
# This will fail with EPERM if /tmp has sticky bit
$ setfattr -n user.tag -v "hello" /tmp/ravis_file.txt
setfattr: /tmp/ravis_file.txt: Operation not permitted
Limits exist at two levels: the VFS layer (applies to all file systems) and individual file system limits (may be more restrictive).
| Limit | VFS Maximum | Notes |
|---|---|---|
| EA name length | 255 characters | Includes the namespace prefix (e.g., “user.”) |
| EA value size | 64 KB (65,536 bytes) | Single EA value cannot exceed this |
File systems can impose tighter constraints than the VFS layer allows.
| File System | Total EA Bytes Per File | Block Sizes Affected | Calculation Note |
|---|---|---|---|
| ext2 / ext3 / ext4 | 1 disk block | 1024, 2048, or 4096 bytes | Sum of all EA names + values must fit in one block |
| JFS | 128 KB | — | More generous than ext*, also supports os2 namespace |
| XFS | ~4 GB theoretical | — | Practically limited by VFS 64 KB value limit |
| Btrfs | Generous | — | Modern CoW design, good EA storage |
Visual: Total EA storage comparison per file
ext4 (4K)
4 KB
JFS
128 KB
VFS limit
64 KB (per single EA value)
When you call setxattr(), you may hit these limits. The kernel returns specific errors:
| Error Code | Meaning |
|---|---|
ENOSPC |
No space left — per-file EA block is full (ext4) |
E2BIG |
Value exceeds the 64 KB VFS limit |
ERANGE |
Buffer too small when reading EA value |
ENODATA |
EA with the given name does not exist |
EPERM |
Operation not permitted (e.g., symlink, sticky dir) |
EACCES |
Permission denied (no read/write on file) |
ENOTSUP |
File system does not support EAs |
#include <sys/xattr.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
/*
* Safely set a user EA with proper error handling.
* Returns 0 on success, -1 on failure.
*/
int safe_set_ea(const char *path, const char *ea_name, const char *value) {
if (setxattr(path, ea_name, value, strlen(value), 0) == -1) {
switch (errno) {
case ENOSPC:
fprintf(stderr, "Error: EA block full on this file (ext4 limit)\n");
break;
case E2BIG:
fprintf(stderr, "Error: EA value too large (>64KB)\n");
break;
case EPERM:
fprintf(stderr, "Error: Not permitted (symlink or sticky dir?)\n");
break;
case EACCES:
fprintf(stderr, "Error: No write permission on file\n");
break;
case ENOTSUP:
fprintf(stderr, "Error: File system does not support EAs\n");
break;
default:
perror("setxattr");
break;
}
return -1;
}
return 0;
}
int main() {
/* Works fine: small value, regular file */
safe_set_ea("/tmp/test.txt", "user.status", "active");
/* This would fail on ext4 if EA block is already full */
char big_value[5000];
memset(big_value, 'A', sizeof(big_value) - 1);
big_value[4999] = '\0';
safe_set_ea("/tmp/test.txt", "user.big_data", big_value);
return 0;
}
Before your program relies on EAs, you can probe whether the file system supports them:
#include <sys/xattr.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
/*
* Returns 1 if the given path's filesystem supports user EAs.
* Returns 0 if not supported.
*/
int ea_supported(const char *path) {
/* Try to set a test EA */
int ret = setxattr(path, "user.__ea_probe__", "1", 1, 0);
if (ret == -1) {
if (errno == ENOTSUP || errno == EOPNOTSUPP)
return 0;
/* Other errors (EACCES etc.) — FS supports EAs but we have other issues */
if (errno == EACCES)
return 1; /* FS supports EAs, we just lack permission */
} else {
/* Clean up the test EA */
removexattr(path, "user.__ea_probe__");
}
return 1;
}
int main() {
const char *paths[] = { "/tmp/test.txt", "/proc/self", NULL };
for (int i = 0; paths[i] != NULL; i++) {
printf("EA support on '%s': %s\n",
paths[i],
ea_supported(paths[i]) ? "YES" : "NO");
}
return 0;
}
JFS supports a fifth namespace called os2, which is not available on any other Linux file system. It exists only to provide backward compatibility with OS/2 file system extended attributes.
- Only available on JFS.
- No privilege required to create os2 EAs.
- Exists for legacy compatibility — not for new development.
/tmp), an unprivileged process cannot set user EAs on a file in that directory if it is owned by a different user. The sticky bit is normally used to prevent users from deleting others’ files; this same protection extends to EA modification.os2 namespace is a special EA namespace supported only on JFS. It provides backward compatibility with OS/2 file system EAs. No privilege is needed to create os2 EAs. It is not available on any other Linux file system.