gypsydave5

The blog of David Wickes, software developer

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?

Permissions

Let’s start with that first expectation.

expect(permissions & 0000100).to be_true

permissions is a variable representing the result of File.stat(folder).mode, where 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 at.

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 commands like 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.

In practice: chmod 777 gives permissions for everything. chmod 644 gives read/write to user, and just read to group and other. 755 is 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 0 (say 0777) will automatically be translated as an octal (0777 will return 511 - the decimal representation). And you can flip to a binary representation with to_s(2) - the (2) setting the base of the conversion, so that 0777.to_s(2) will return "111111111". Try some other numbers!)

So when 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 777 and 111111111).

Bitwise

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 111 and 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 0. So 111 & 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 1 is true and 0 is 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 cd into 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 number 100 (all those 0s at the beginning are just saying ‘hey! I’m octal!’ to Ruby).

Converting octal to binary, 700 is 111000000, and 100 is 1000000 (Don’t trust me? Fire up a repl!). Maybe think of 1000000 as 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 64.

The neat thing about this is that it will return 64 for any permission set that includes user executable permission on a file - 700, 500, 177, 355, 777 – they all work. Say we’ve got 355 - in binary that’s 011101101. &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 010 becoming 000001000 and 001 to 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 change 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