I am trying to write a small C program that emulates the unix command
ls -l
. To do so, I am using the stat(2)
syscall and have ran into a small hiccup writing the permissions. I have a mode_t
variable which holds the file permissions from st_mode
, and it wouldn't be hard to parse that value into s string representation, but I was just wondering if there is a better way to be doing it than that.

- 345
- 1
- 2
- 9
3 Answers
example from google
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
if(argc != 2)
return 1;
struct stat fileStat;
if(stat(argv[1], &fileStat) < 0)
return 1;
printf("Information for %s\n", argv[1]);
printf("---------------------------\n");
printf("File Size: \t\t%d bytes\n", fileStat.st_size);
printf("Number of Links: \t%d\n", fileStat.st_nlink);
printf("File inode: \t\t%d\n", fileStat.st_ino);
printf("File Permissions: \t");
printf( (S_ISDIR(fileStat.st_mode)) ? "d" : "-");
printf( (fileStat.st_mode & S_IRUSR) ? "r" : "-");
printf( (fileStat.st_mode & S_IWUSR) ? "w" : "-");
printf( (fileStat.st_mode & S_IXUSR) ? "x" : "-");
printf( (fileStat.st_mode & S_IRGRP) ? "r" : "-");
printf( (fileStat.st_mode & S_IWGRP) ? "w" : "-");
printf( (fileStat.st_mode & S_IXGRP) ? "x" : "-");
printf( (fileStat.st_mode & S_IROTH) ? "r" : "-");
printf( (fileStat.st_mode & S_IWOTH) ? "w" : "-");
printf( (fileStat.st_mode & S_IXOTH) ? "x" : "-");
printf("\n\n");
printf("The file %s a symbolic link\n", (S_ISLNK(fileStat.st_mode)) ? "is" : "is not");
return 0;
}
result:
Information for 2.c --------------------------- File Size: 1223 bytes Number of Links: 1 File inode: 39977236 File Permissions: -rw-r--r-- The file is not a symbolic link

- 2,438
- 22
- 37
-
2Thanks for the answer. This has helped a ton. – cheezone Apr 25 '12 at 20:45
-
2Note that because the code uses `stat()` instead of `lstat()`, the only time it will report 'symlink' is when the symlink is broken. Otherwise, it will report on the file at the end of the symlink. – Jonathan Leffler Jan 26 '15 at 13:37
-
nice, but spaces after commas makes it more readable rather than everything crammed together. – clearlight Aug 31 '18 at 12:09
-
How to know the owner's and group's name, may i know? please – krishnaacharyaa Jan 13 '21 at 13:03
The basics are simple enough; the tricky bits are the SUID and SGID bits and the sticky bit, which modify the 'x' bits. Consider splitting the permissions into 3 octal digits for user, group, owner, and using those to index into an array of 3-character strings such as rwx
and ---
. Then adjust the appropriate x
bits based on the other mode bits. The file type will have to be dealt with separately, but you could use a 12-bit shift right (possibly with masking) and a 16 entry table to deal with the 16 possible values (not all of which are valid on any given system). Or you can deal with known types as shown in the code below.
+----+---+---+---+---+
|type|SSS|USR|GRP|OTH|
+----+---+---+---+---+
The 4 types bits, the three S-bits (setuid, setgid, sticky) and the user, group and other bits.
This is code I use for converting mode_t
into a string. It was written for a nicely threadless program, so it uses static data; it would be trivial to modify it to take the output string as an input parameter:
/* Convert a mode field into "ls -l" type perms field. */
static char *lsperms(int mode)
{
static const char *rwx[] = {"---", "--x", "-w-", "-wx",
"r--", "r-x", "rw-", "rwx"};
static char bits[11];
bits[0] = filetypeletter(mode);
strcpy(&bits[1], rwx[(mode >> 6)& 7]);
strcpy(&bits[4], rwx[(mode >> 3)& 7]);
strcpy(&bits[7], rwx[(mode & 7)]);
if (mode & S_ISUID)
bits[3] = (mode & S_IXUSR) ? 's' : 'S';
if (mode & S_ISGID)
bits[6] = (mode & S_IXGRP) ? 's' : 'l';
if (mode & S_ISVTX)
bits[9] = (mode & S_IXOTH) ? 't' : 'T';
bits[10] = '\0';
return(bits);
}
static int filetypeletter(int mode)
{
char c;
if (S_ISREG(mode))
c = '-';
else if (S_ISDIR(mode))
c = 'd';
else if (S_ISBLK(mode))
c = 'b';
else if (S_ISCHR(mode))
c = 'c';
#ifdef S_ISFIFO
else if (S_ISFIFO(mode))
c = 'p';
#endif /* S_ISFIFO */
#ifdef S_ISLNK
else if (S_ISLNK(mode))
c = 'l';
#endif /* S_ISLNK */
#ifdef S_ISSOCK
else if (S_ISSOCK(mode))
c = 's';
#endif /* S_ISSOCK */
#ifdef S_ISDOOR
/* Solaris 2.6, etc. */
else if (S_ISDOOR(mode))
c = 'D';
#endif /* S_ISDOOR */
else
{
/* Unknown type -- possibly a regular file? */
c = '?';
}
return(c);
}

- 730,956
- 141
- 904
- 1,278
-
Appreciate the depth provided by your answer! Learn something new every day! – cheezone Apr 25 '12 at 20:46
-
@ArranCudbard-Bell: I've been cleaning up a corpus of commands with something analogous to `clang -Weverything` and it can be a bit painful at times. I've not actually tried `clang -Weverything` directly; it might be less onerous than the options I'm using (about 18 `-W*` flags; `-Wconversion` is the biggest cause of trouble for my code). – Jonathan Leffler Jan 26 '15 at 15:04
-
@JonathanLeffler -Weverything then -Wno- out the really stupid warnings. I think there's one where it warns you if you use a default case in a switch over an enum type. You'll never fix them all. Pro tip for the consts. Put it on the right, then read right to left. const then always applies to the thing directly on its left. Helps with const arrays of consts. – Arran Cudbard-Bell Jan 26 '15 at 19:10
-
@Jonathan Leffler - there seems to be small issue with sticky bit: your code shows me `drwx-wx--t`, where `ls` shows `drwx-wx--T`. For example in case of `/var/spool/cron/crontabs/`. – Martin Vegter Dec 17 '16 at 20:56
-
1@MartinVegter: yup, looks like the sequence S_IXUSR, S_IXGRP, S_IXUSR should be S_IXUSR, S_IXGRP, S_IXOTH. I'll fix it. – Jonathan Leffler Dec 17 '16 at 20:58
I don't like the if/ else if
syntax.
I prefer use the switch
statement. After struggling a bit I found the way we can do it using different macros, for example:
S_ISCHR (mode)
Is equivalent to:
((mode & S_IFMT) == S_IFCHR)
This allows us to build a switch statement like this:
char f_type(mode_t mode)
{
char c;
switch (mode & S_IFMT)
{
case S_IFBLK:
c = 'b';
break;
case S_IFCHR:
c = 'c';
break;
case S_IFDIR:
c = 'd';
break;
case S_IFIFO:
c = 'p';
break;
case S_IFLNK:
c = 'l';
break;
case S_IFREG:
c = '-';
break;
case S_IFSOCK:
c = 's';
break;
default:
c = '?';
break;
}
return (c);
}
Which in my opinion it's a bit more elegant than the if/else if
approach.

- 1,291
- 17
- 16
-
1Have you dumped the assembly code to determine which is more efficient? (e.g. `gcc -S -masm=intel -O2 -o filemode.asm filemode.c`) – David C. Rankin Jun 16 '17 at 05:23