UNIX, tell me if I can execute this file

A surprisingly complicated question on a UNIX system, in fact. UNIX files have three relevant execution bits. One for owner, group and other. To test if a file is executable at all, you can stat it, and simply test the mode bit-field - consider this short C program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <sys/stat.h>
#include <sys/types.h>
 
#include <err.h>
#include <stdlib.h>
#include <stdio.h>
 
int
main(int argc, char **argv)
{
        struct stat sb;
 
        if (argc < 2)
                errx(1, "supply a filename");
 
        if (stat(argv[1], &sb) == -1)
                err(1, "stat %s", argv[1]);
 
        if (sb.st_mode 
            & (S_IXUSR|S_IXGRP|S_IXOTH))
                printf("%s is executable by "
                    "owner, group or other\n",
                     argv[1]);
        else
                printf("%s is not executable\n",
                    argv[1]);
 
        exit(0);
}

Now we run it against some very common UNIX files:


$ ./isexec $(which sh)
/bin/sh is executable by owner, group or other
$ ./isexec /etc/passwd
/etc/passwd is not executable

This still doesn’t tell us if the file is executable by our user. To find out, we must first find our relevant user ids and all our group memberships. We can do this quite nicely with the getresuid and getgroups functions. You may ask why I use getresuid() instead of the superficially simpler getuid() function. The answer is that the value getuid() returns can be ambiguous, getresuid() is much more explicit about what the real, effective and saved user IDs are. It makes code clearer and reduces the opportunity for subtle bugs. We add code like the following to the start of our program:

1
2
3
4
5
6
7
8
        gid_t mygroups[NGROUPS_MAX];
        uid_t r, e, s;
 
        if (getgroups(0, mygroups) == -1)
                err(1, "getgroups failure");
        if ((ngroups = getresuid(&r, &e, &s))
            == -1)
                err(1, "getresuid failure");

Now we have our uid and our group ids, we can test against the owner, group and mode bit-field of the file. If we are the owner, and the owner execute bit is not set, we may not execute it - even if the group or other execute bits are set in our favour. Same with group - if we are in the same group as the file, but group execute bit is not set, we may not execute it - unless we are the owner and the owner execute bit is set. This is may seem a little bit complicated to code at first. The important thing is to get the order right:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        if (euid == sb.st_uid)
                if (sb.st_mode & S_IXUSR)
                        goto exec;
                else
                        goto noexec;
 
         for (j = 0; j < ngroups; j++)
                if (mygroups[j] == sb.st_gid)
                        if (sb.st_mode & S_IXGRP)
                                goto exec;
                        else
                                goto noexec;
         if (sb.st_mode & S_IXOTH)
                 goto exec;
         goto noexec;
exec:
         printf("file is executable by you\n");
         return (0);
noexec:
         printf("file is not executable by you\n");
         return (0);

The use of the goto keyword here in fact increases legibility, since it means you don’t have to have a whole bunch of ugly conditionals.

You may wonder why I bothered to write about this subject. Well, the answer is that I needed such an algorithm in order to add auto-completion to the cwm window manager. I committed the code to OpenBSD’s Xenocara tree a few days ago, so that now the exec dialog scans the default PATH, determines which files you may execute, and populates the execution menu with these values, such that you get auto-completion (aka type-ahead-find) when executing programs.

Share this on a social bookmarking site:
  • Digg
  • del.icio.us
  • Netvouz
  • description
  • ThisNext
  • MisterWong
  • Reddit
  • StumbleUpon

Tags: , ,

Related posts:
  • Decoupled Python GUI Construction, or BitTorrent visualisation
  • Pylons tip #2: Using SQLite with Pylons
  • BitTorrent basics - protocol etc
  • Vi(M) tips #1
  • Top 10 Torrented Films of 2007
  • Leave a Reply