Printing file permissions like 'ls -l' using stat(2) in C
Asked Answered
C

3

22

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.

Crotchety answered 25/4, 2012 at 20:15 Comment(0)
H
73

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
Honeysweet answered 25/4, 2012 at 20:20 Comment(4)
Thanks for the answer. This has helped a ton.Crotchety
Note 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.Irredentist
nice, but spaces after commas makes it more readable rather than everything crammed together.Mons
How to know the owner's and group's name, may i know? pleaseTristich
I
21

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);
}
Irredentist answered 25/4, 2012 at 20:20 Comment(5)
Appreciate the depth provided by your answer! Learn something new every day!Crotchety
@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).Irredentist
@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.Driskell
@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/.Tilt
@MartinVegter: yup, looks like the sequence S_IXUSR, S_IXGRP, S_IXUSR should be S_IXUSR, S_IXGRP, S_IXOTH. I'll fix it.Irredentist
T
1

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.

Tremulant answered 16/6, 2017 at 4:12 Comment(1)
Have you dumped the assembly code to determine which is more efficient? (e.g. gcc -S -masm=intel -O2 -o filemode.asm filemode.c)Incorrect

© 2022 - 2024 — McMap. All rights reserved.