Changing the Permissions on a File
Problem
You want to control access to a file by modifying its Unix permissions. For instance, you want to make it so that everyone on your system can read a file, but only you can write to it.
Solution
Unless you've got a lot of Unix experience, it's hard to remember the numeric codes for the nine Unix permission bits. Probably the first thing you should do is define constants for them. Here's one constant for every one of the permission bits. If these names are too concise for you, you can name them USER_READ, GROUP_WRITE, OTHER_ EXECUTE, and so on.
class File U_R = 0400 U_W = 0200 U_X = 0100 G_R = 0040 G_W = 0020 G_X = 0010 O_R = 0004 O_W = 0002 O_X = 0001 end
You might also want to define these three special constants, which you can use to set the user, group, and world permissions all at once:
class File A_R = 0444 A_W = 0222 A_X = 0111 end
Now you're ready to actually change a file's permissions. Every Unix file has a permission bitmap, or mode, which you can change (assuming you have the permissions!) by calling File.chmod. You can manipulate the constants defined above to get a new mode, then pass it in along with the filename to File.chmod.
The following three chmod calls are equivalent: for the file my_file, they give readwrite access to to the user who owns the file, and restrict everyone else to read-only access. This is equivalent to the permission bitmap 11001001, the octal number 0644, or the decimal number 420.
open("my_file", "w") {} File.chmod(File::U_R | File::U_W | File::G_R | File::O_R, "my_file") File.chmod(File::A_R | File::U_W, "my_file") File.chmod(0644, "my_file") # Bitmap: 110001001 File::U_R | File::U_W | File::G_R | File::O_R # => 420 File::A_R | File::U_W # => 420 0644 # => 420 File.lstat("my_file").mode & 0777 # => 420
Note how I build a full permission bitmap by combining the permission constants with the OR operator (|).
Discussion
A Unix file has nine associated permission bits that are consulted whenever anyone tries to access the file. They're divided into three sets of three bits. There's one set for the user who owns the file, one set is for the user group who owns the file, and one set is for everyone else.
Each set contains one bit for each of the three basic things you might do to a file in Unix: read it, write it, or execute it as a program. If the appropriate bit is set for you, you can carry out the operation; if not, you're denied access.
When you put these nine bits side by side into a bitmap, they form a number that you can pass into File.chmod. These numbers are difficult to construct and read without a lot of practice, which is why I recommend you use the constants defined above. It'll make your code less buggy and more readable.[2]
[2] It's true that it's more macho to use the numbers, but if you really wanted to be macho you'd be writing a shell script, not a Ruby program.
File.chmod completely overwrites the file's current permission bitmap with a new one. Usually you just want to change one or two permissions: make sure the file isn't world-writable, for instance. The simplest way to do this is to use File.lstat#mode to get the file's current permission bitmap, then modify it with bit operators to add or remove permissions. You can pass the result into File.chmod.
Use the XOR operator (^) to remove permissions from a bitmap, and the OR operator, as seen above, to add permissions:
# Take away the world's read access. new_permission = File.lstat("my_file").mode ^ File::O_R File.chmod(new_permission, "my_file") File.lstat("my_file").mode & 0777 # => 416 # 0640 octal # Give everyone access to everything new_permission = File.lstat("my_file").mode | File::A_R | File::A_W | File::A_X File.chmod(new_permission, "my_file") File.lstat("my_file").mode & 0777 # => 511 # 0777 octal # Take away the world's write and execute access new_permission = File.lstat("my_file").mode ^ (File::O_W | File::O_X) File.chmod(new_permission, "my_file") File.lstat("my_file").mode & 0777 # => 508 # 0774 octal
If doing bitwise math with the permission constants is also too complicated for you, you can use code like this to parse a permission string like the one accepted by the Unix chmod command:
class File def File.fancy_chmod(permission_string, file) mode = File.lstat(file).mode permission_string.scan(/[ugoa][+-=][rwx]+/) do |setting| who = setting[0..0] setting[2..setting.size].each_byte do |perm| perm = perm.chr.upcase mask = eval("File::#{who.upcase}_#{perm}") (setting[1] == ?+) ? mode |= mask : mode ^= mask end end File.chmod(mode, file) end end # Give the owning user write access File.fancy_chmod("u+w", "my_file") File.lstat("my_file").mode & 0777 # => 508 # 0774 octal # Take away the owning group's execute access File.fancy_chmod("g-x", "my_file") File.lstat("my_file").mode & 0777 # => 500 # 0764 octal # Give everyone access to everything File.fancy_chmod("a+rwx", "my_file") File.lstat("my_file").mode & 0777 # => 511 # 0777 octal # Give the owning user access to everything. Then take away the # execute access for users who aren't the owning user and aren't in # the owning group. File.fancy_chmod("u+rwxo-x", "my_file") File.lstat("my_file").mode & 0777 # => 510 # 0774 octal
Unix-like systems such as Linux and Mac OS X support the full range of Unix permissions. On Windows systems, the only one of these operations that makes sense is adding or subtracting the U_W bit of a filemaking a file read-only or not. You can use File.chmod on Windows, but the only bit you'll be able to change is the user write bit.
See Also
- Recipe 6.2, "Checking Your Access to a File"
- Recipe 23.9, "Normalizing Ownership and Permissions in User Directories"