One of the things that really irked me as I started to learn more about Linux was using the chmod
command.
When I first learned the chmod
command in school, we learned to explicity declare the permissions:
$ chmod a+x file.txt
However, whenever running through tutorials or, really, anything that involved chmod
, I found that most people like to use the numeric (octal) format instead1:
$ chmod 777 file.txt
I knew there was some kind of math involved with chmod
, so I decided it would be fun to try writing a chmod
calculator in Go, a language I started learning this summer.
But before I could actually write it, I had to understand how the chmod
permissions worked. (If you’re only interested in the Go, skip ahead.)
Understanding Unix permissions2
File permissions can be represented using 9 characters. For example:
r-x---rwx
Since it’s 9 characters long, we can easily split it into 3 roles: user, group, and others.
user
: the owner of the filegroup
: the group the file is assigned toothers
: users that fall into neither ofuser
orgroup
(user) (group) (others)
r-x --- rwx
Within each role, we then have 3 available slots for actions3:
r
: readw
: write 3x
: execute, or
-
: which means you can’t perform an action
So in our example above, we have:
user
(owner) mayr
ead and ex
ecute the file, but notw
ritegroup
cannotr
ead/w
rite/ex
ecute the fileothers
mayr
ead,w
rite, and ex
ecute the file.
Converting permissions to numbers
Looking at our example again:
r-x---rwx
We know that each slot has only two possibilities: either a letter (r
/w
/x
, depending on which slot it is) or a hyphen (-
).
Instead of using letters and hyphens then, we can use binary to show whether an action is allowed for a particular role. For each disallowed action (indicated by the -
character), we can set it as a 0
in binary. Otherwise, we can set a 1
.
Let’s convert our example permissions:
r-x---rwx
101000111
“Great,” you say. “101000111 converted to decimal is 327, so that’s our magic number, right? We’re done!”
Not quite.
We want to be able to understand the permissions each role has a little better. 327
tells us what all the permissions are, but it’s not very useful for seeing at a glance what each role’s permissions are.
Let’s try converting the binary for each role instead:
r-x --- rwx
101 000 111
5 0 7
Now, by converting the number role by role, we know each digit will tell us what permissions each role has, with no guessing. If a role has all permissions, it will always have the digit 7
, for example.
Coincidentally, converting the number role by role is actually the same as converting it into octal, since there are 3 digits in a role (= 8 possibilities, from 0 to 7). If we run 101000111 through a binary → octal conversion, we can see that it is in fact, 507.
Coding it up
We now know how to properly convert from octal permissions to “symbolic” permissions (the letters) and vice versa.
From octal to symbols:
- Convert the octal number to a binary string
- Parse the binary string, putting the correct “action” character (i.e.
r
/w
/x
) if the current character is a1
, or a-
if it’s a0
To put this into code, we can take advantage of Go’s packages, like strconv
. strconv
offers a couple very useful functions that we can leverage:
ParseInt
FormatInt
So to convert from octals to permissions, we can use ParseInt
to interpret a command-line arg as an octal number:
octal_num, err := strconv.ParseInt(os.Args[1], 8, 0)
Then, we can use FormatInt
to format that number as a binary string4:
binary_str := strconv.FormatInt(octal_num, 2)
Finally, we can iterate through the binary string and construct our symbolic permissions, using a for
loop and a switch
statement:
var perms string
for i := 0; i < 9; i++ {
switch {
case binary_str[i] == '0':
perms += "-"
case i%3 == 0:
perms += "r"
case i%3 == 1:
perms += "w"
case i%3 == 2:
perms += "x"
default:
return "", errors.New("unable to parse octal")
}
}
And that’s it! When we print out perms
, we will have the symbolic permissions that the “magic” octal number represents.
Moving Forward
So far, I’ve coded up taking the octal permissions and converting it to symbols, as well as taking the symbols and converting them to octals.
This was a great way to get me familiar with manipulating strings and numbers of different bases in Go, and there’s probably (? definitely?) more Go-ish ways to do it5.
In the future, I want to take some time to:
- Understand how the
setgid
/setuid
/sticky
bits work!- I kind of know how the
sticky
bit works (only certain people can deletesticky
files), but not the other two
- I kind of know how the
- Understand how Go tests work.
But for now I’m just happy I have a proper way of translating chmod
numbers into readable permissions.
-
Later on (like - really later on, this year, actually) I discovered that passing
-v
, or--verbose
would show what the explicit permissions are.↩︎$ chmod -v 555 file.txt mode of 'file.txt' changed from 0777 (rwxrwxrwx) to 0555 (r-xr-xr-x)
-
Julia Evans also has an amazing zine about Unix permissions. She’s also much more succinct than I. ↩︎
-
There are also the
setgid
/setuid
/sticky
actions; however, I’m not going to go into those for now. ↩︎ -
For extra safety, you may need to pad the beginning of the string with 0s. I did so like this:
↩︎if len(binary_str) < 9 { padding := 9 - len(binary_str) for i := 0; i < padding; i++ { binary_str = "0" + binary_str } }
-
Yes, I know I should read Effective Go, but it’s very long and I feel like I should always be taking notes whenever I try reading it… ↩︎
-
That being said, the Exercism Go track is one of the best-mentored tracks on the site, and I would highly encourage you to work through it. ↩︎