Bitwise and Permission
So I thought I’d try and write about what happened last day of the first week at Makers, as mentioned in my last post, about the failing RSpec test for the Unix task. Just because I learned a lot about both permissions on a Unix like system, about bitwise operators in Ruby, about how RSpec works, and how (not) to fix things.
So… the test checking file permissions in a directory by running the following RSpec test.
it "Should only allow the owner to change into my/private/files" do folder = "my/private/files" permissions = File.stat(folder).mode expect(permissions & 0000100).to be_true expect(permissions & 0000010).to eq(0) expect(permissions & 0000001).to eq(0) end
When we first ran it, it failed on the first expectation above - even though we knew that the file had the right permissions. So what was going on - both in terms of how it was meant to work, and why it wasn’t working?
Let’s start with that first expectation.
expect(permissions & 0000100).to be_true
permissions is a variable representing the result of
folder is the directory being checked. The
mode method returns the
permissions-bits of the Unix
stat command - which you can take
a look at in detail with a quick
man stat -a (as it’s
stat(2) you’re looking
What does all this mean? It means that we’re getting back an integer which
represents the current permissions on the file. And how does an integer
represent permissions? If you’ve been using
chmod to change permission with
chmod go-w filename (remove write from group and others), well
- you’ve been missing a trick and a whole lot of fun.
The fun way to set permission is using the absolute mode, which sets the
permissions absolutely every time you use it (rather than relatively removing or
adding them from the current state - see
man chmod for more). There are three
settings (read, write and execute) for each of the three permissions groups
(user, group, other) on a file. Each of those permissions sets is represented
by a single octal digit (from 1 to 7), and the settings are literally added to
each one - the setting for read is 4, write is 2 and execute is 1.
Any combination of these numbers will produce a unique number - read + write = 6, write + execute = 3, just read = 4. And you can combine them into a three-digit octal number which represents the file permissions for any given file, where the first digit is user, the second is group and the third is other.
chmod 777 gives permissions for everything.
chmod 644 gives
read/write to user, and just read to group and other.
read/write/execute for user, and just read/execute for the group and other.
The science “bit”
Notice how none of the octal numbers ever ‘carry’ over to the next one when they’re added together? This is a piece of computer science wizzardry - because they’re in octal, I can look at them as both an actual integer or as a series of switches (or maybe dials), setting permissions for each permissions group up and down. Thing is, for your computer, these two ways of looking at it are exactly the same.
777 in decimal notation is
511 (do the maths if you like), but also has
a binary representation of…
111111111. Hey, look - all the 1s! It’s like all
the switches are turned on - and they really are!. This is because octal digits
map really neatly to binary digits - they’re a three-digit long collection of
binary digits. ‘Binary digit’ is a bit of a mouthful (pun intended), so let’s
use the shorthand word - bit. Each set of three bits represents one of the
permission statuses for a particular permissions set. So the first three 1s
above are the permissions for the user, and in particular the first one is the
read, second write and the third the execute - all set to 1 or ‘on’.
(Experiment in a Ruby repl like pry or irb - you can switch between binary,
octal and decimal really quickly in Ruby. Any integer you type in with a leading
0777) will automatically be translated as an octal (
511 - the decimal representation). And you can flip to a binary
to_s(2) - the
(2) setting the base of the conversion, so
0777.to_s(2) will return
"111111111". Try some other numbers!)
mode returns an integer, it’s the integer that represents the current
permissions on that file - a file with read, write and exeute permissions for
all the sets would give you the number
511 (which is the same as
Now we get to the fun stuff -
permissions & 0000100. What’s the
& doing? And
those leading 0s? As mentioned above, the leading zeroes are just Ruby’s way of
saying that this number is in octal. So (repls open)
0000100 just becomes
64. But the thing doing the work here is
& - not our friendly Boolean
but a differnent beast - this is the bitwise AND.
Bitwise operators, of which
& is an example, really tear the lid off the
computer and get a little bit closer to the bare metal. Computers aren’t made of
objects, or lines of code, strings and integers. They’re made of 0s and 1s. And
bitwise operators work on 0s and 1s - or, specifically, binary numbers.
Let’s take a pair of binary numbers, say
100 (known to you and me as
7 and 4). Bitwise AND compares the bit in each position, and asks the question
“are you both
1?” If they are, you get a
1, otherwise it’s a
& 100 will return
100 as the third bit (counting from the right) is the only
one that matches in each number
(Think of it just like a regular logical AND - but
false - running on each bit position.)
We now have enough knowledge to look at the test again
Back to the test
To recap, the test looks like this:
”“ruby it “Should only allow the owner to change into my/private/files” do folder = “my/private/files” permissions = File.stat(folder).mode expect(permissions & 0000100).to be_true expect(permissions & 0000010).to eq(0) expect(permissions & 0000001).to eq(0) end
The key line being: ```ruby expect(permissions & 0000100).to be_true
Now let’s pretend that the directory currently has permissions of
700 - read,
write and execute (‘execute’ is ‘open’ for a folder – allowing you to
it). We run
mode on it and get the permissions integer back - in octal, that’s
700 again. We then run a bitwise AND - the
& against it using the octal
100 (all those
0s at the beginning are just saying ‘hey! I’m octal!’
Converting octal to binary,
trust me? Fire up a repl!). Maybe think of
001000000 for the next
bit. Comparing the two binary numbers, the only place they match is at the
seventh bit - and so we get
1000000 as the returned value. Which, in octal is
100 and in decimal is
The neat thing about this is that it will return
64 for any permission set
that includes user executable permission on a file -
777 – they all work. Say we’ve got
355 - in binary that’s
&ing it with
001000000 again will give us…
001000000 again - hey, it’s
64! Try it with as many numbers as you like.
The other two tests are to check whether the Group or Other sets also have
execute permission – that’s
000000001, bitwised against the permissions making sure they don’t have that
bit set. They come out as
0 – no matches!
And so that’s how it all hangs together. But why didn’t it work? Simply put
everyone on the course had just installed the latest version of RSpec, and
be_true is not in the latest version’s syntax. So all we needed to do was
be_true over to
eq(64) and it would’ve been fixed.
Of course, that’s not what we actually did. What we did was hack around until
it worked, and even then we ended up with
is_not eq(0) instead of the neater
(and more correct) answer of
eq(64). It was only when thinking about it over
the weekend that I really got a handle on what was going on