Overview
Ruby provides extensive file permission management through the File class and File::Stat objects. File permissions control access to files and directories using a three-tier system: owner, group, and others. Each tier has read, write, and execute permissions represented as octal values or symbolic constants.
The File class offers methods to check, modify, and analyze file permissions. Ruby represents permissions using octal notation (like 0644) or symbolic constants (File::RDWR, File::CREAT). The File::Stat class provides detailed permission information through bitmask operations and convenience methods.
Ruby's permission system works across Unix-like systems and Windows, though Windows has different underlying permission models. The interface remains consistent while Ruby handles platform-specific translation.
# Check if file is readable
File.readable?('/etc/passwd')
# => true
# Get detailed permission info
stat = File.stat('example.txt')
stat.mode.to_s(8)
# => "100644"
Permission operations involve checking current permissions, modifying them with chmod/chown, and handling permission-related errors. Ruby also provides methods to test specific permission combinations and create files with predetermined permissions.
# Create file with specific permissions
File.open('secure.txt', 'w', 0600) do |f|
f.write("confidential data")
end
# Verify permissions were set correctly
File.stat('secure.txt').mode & 0777
# => 384 (0600 in decimal)
Basic Usage
File permission checking uses predicate methods that return boolean values. These methods test permissions for the current process's effective user and group IDs.
# Basic permission checks
File.readable?('config.yml') # => true
File.writable?('logs/app.log') # => true
File.executable?('scripts/deploy') # => true
# Check if file exists and has permissions
if File.readable?('data.json')
content = File.read('data.json')
end
The File.stat method returns a File::Stat object containing comprehensive permission information. The mode attribute represents permissions as a bitmask combining file type and permission bits.
stat = File.stat('example.txt')
# Get full mode (includes file type bits)
stat.mode
# => 33188
# Get just permission bits
permissions = stat.mode & 0777
permissions.to_s(8)
# => "644"
# Check specific permission patterns
stat.mode & 0400 != 0 # owner read
# => true
stat.mode & 0200 != 0 # owner write
# => true
stat.mode & 0100 != 0 # owner execute
# => false
File.chmod changes file permissions using octal notation. The method accepts either a single file path or multiple paths for batch operations.
# Change single file permissions
File.chmod(0755, 'script.rb')
# Change multiple files
File.chmod(0644, 'file1.txt', 'file2.txt', 'file3.txt')
# Verify the change
File.stat('script.rb').mode.to_s(8)
# => "100755"
Creating files with specific permissions uses the mode parameter in File.open or File.new. The umask affects the final permissions, so explicitly setting permissions after creation ensures accuracy.
# Create with permissions (affected by umask)
File.open('temp.txt', 'w', 0666) do |f|
f.write("temporary data")
end
# Set exact permissions after creation
File.chmod(0666, 'temp.txt')
Error Handling & Debugging
Permission-related errors occur when operations exceed the current user's access rights. Ruby raises specific exception types that applications should handle appropriately.
The most common permission error is Errno::EACCES (Permission denied), raised when attempting unauthorized file operations. This exception occurs during file reading, writing, or permission modification.
begin
File.open('/root/private.txt', 'r')
rescue Errno::EACCES => e
puts "Access denied: #{e.message}"
# Handle gracefully - log, use alternative, or request elevation
rescue Errno::ENOENT => e
puts "File not found: #{e.message}"
end
Errno::EPERM occurs when attempting to change ownership or permissions without sufficient privileges. This typically happens when non-root users try to change file ownership or set special permission bits.
begin
File.chown(0, 0, 'system_file.txt')
rescue Errno::EPERM => e
puts "Operation not permitted: #{e.message}"
# Log the attempt or request administrator action
rescue Errno::ENOENT => e
puts "File does not exist: #{e.message}"
end
Permission debugging requires understanding the effective user ID, group memberships, and umask settings. These factors determine what operations the current process can perform.
# Debug current process permissions
puts "Effective UID: #{Process.euid}"
puts "Effective GID: #{Process.egid}"
puts "Groups: #{Process.groups}"
# Check umask (affects new file permissions)
old_umask = File.umask
puts "Current umask: #{old_umask.to_s(8)}"
File.umask(old_umask) # Restore original
Testing permission scenarios requires creating files with known permissions and verifying behavior across different user contexts.
def test_permission_scenario(file_path, expected_mode)
begin
actual_mode = File.stat(file_path).mode & 0777
if actual_mode == expected_mode
puts "✓ Permissions correct: #{expected_mode.to_s(8)}"
else
puts "✗ Permission mismatch: expected #{expected_mode.to_s(8)}, got #{actual_mode.to_s(8)}"
end
rescue Errno::ENOENT
puts "✗ File does not exist: #{file_path}"
rescue Errno::EACCES
puts "✗ Cannot access file: #{file_path}"
end
end
Directory permission errors require understanding that directory traversal needs execute permission, while listing contents needs read permission.
# Directory permission debugging
def check_directory_access(dir_path)
can_read = File.readable?(dir_path)
can_write = File.writable?(dir_path)
can_execute = File.executable?(dir_path)
puts "Directory: #{dir_path}"
puts " Readable (can list): #{can_read}"
puts " Writable (can create files): #{can_write}"
puts " Executable (can traverse): #{can_execute}"
if can_execute && !can_read
puts " Warning: Can traverse but not list contents"
elsif can_read && !can_execute
puts " Warning: Cannot access directory contents"
end
end
Production Patterns
Production file permission management requires balancing security, functionality, and maintainability. Applications must handle permissions correctly across deployment environments while maintaining security boundaries.
Web applications typically run under restricted user accounts with specific permission requirements. Files must be readable by the application user while remaining secure from other system users.
class SecureFileManager
def self.setup_application_files(app_root)
# Set directory permissions for application structure
File.chmod(0755, app_root)
File.chmod(0755, File.join(app_root, 'public'))
File.chmod(0700, File.join(app_root, 'tmp'))
File.chmod(0700, File.join(app_root, 'log'))
# Secure configuration files
config_files = Dir.glob(File.join(app_root, 'config', '*.yml'))
config_files.each { |f| File.chmod(0600, f) }
# Make scripts executable
script_files = Dir.glob(File.join(app_root, 'bin', '*'))
script_files.each { |f| File.chmod(0755, f) }
end
def self.verify_permissions(app_root)
issues = []
# Check critical directories
%w[tmp log].each do |dir|
dir_path = File.join(app_root, dir)
unless File.writable?(dir_path)
issues << "#{dir} directory not writable"
end
end
# Check configuration security
config_files = Dir.glob(File.join(app_root, 'config', '*.yml'))
config_files.each do |file|
mode = File.stat(file).mode & 0777
if mode & 0077 != 0 # Check group/other permissions
issues << "#{file} has overly permissive permissions: #{mode.to_s(8)}"
end
end
issues
end
end
Log rotation and file cleanup require careful permission handling to prevent security issues and ensure continued operation.
class LogRotationManager
def initialize(log_dir, app_user)
@log_dir = log_dir
@app_user = app_user
end
def rotate_logs
log_files = Dir.glob(File.join(@log_dir, '*.log'))
log_files.each do |log_file|
next unless File.writable?(log_file)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
archived_name = "#{log_file}.#{timestamp}"
# Rotate the file
File.rename(log_file, archived_name)
# Create new log file with proper permissions
File.open(log_file, 'w', 0640) do |f|
f.write("Log rotation at #{Time.now}\n")
end
# Compress old log
system("gzip #{archived_name}")
end
end
def cleanup_old_logs(days_to_keep = 30)
cutoff_time = Time.now - (days_to_keep * 24 * 60 * 60)
archived_logs = Dir.glob(File.join(@log_dir, '*.log.*.gz'))
archived_logs.each do |log_file|
if File.mtime(log_file) < cutoff_time
File.unlink(log_file)
end
end
end
end
Database backup security requires setting restrictive permissions on backup files while ensuring the backup process can execute successfully.
class DatabaseBackupManager
def initialize(backup_dir)
@backup_dir = backup_dir
ensure_backup_directory
end
def create_backup(database_name)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
backup_file = File.join(@backup_dir, "#{database_name}_#{timestamp}.sql")
# Create backup with restrictive permissions
File.open(backup_file, 'w', 0600) do |f|
# Database backup logic would write to f
f.write("-- Backup created at #{Time.now}\n")
end
# Verify permissions were set correctly
actual_mode = File.stat(backup_file).mode & 0777
unless actual_mode == 0600
File.chmod(0600, backup_file) # Ensure security
end
backup_file
end
private
def ensure_backup_directory
unless File.directory?(@backup_dir)
Dir.mkdir(@backup_dir)
end
# Secure backup directory
File.chmod(0700, @backup_dir)
end
end
Configuration management systems need to handle sensitive files with appropriate permissions while allowing automated deployment processes to function.
class ConfigurationDeployer
def deploy_configuration(source_dir, target_dir)
config_mapping = {
'database.yml' => 0600, # Database credentials
'secrets.yml' => 0600, # Application secrets
'application.yml' => 0644, # General application config
'nginx.conf' => 0644 # Web server config
}
config_mapping.each do |filename, permissions|
source_file = File.join(source_dir, filename)
target_file = File.join(target_dir, filename)
if File.exist?(source_file)
FileUtils.cp(source_file, target_file)
File.chmod(permissions, target_file)
puts "Deployed #{filename} with permissions #{permissions.to_s(8)}"
else
puts "Warning: #{filename} not found in source directory"
end
end
end
end
Reference
File Permission Methods
Method | Parameters | Returns | Description |
---|---|---|---|
File.readable?(path) |
path (String) |
Boolean |
Tests if file is readable by effective user |
File.writable?(path) |
path (String) |
Boolean |
Tests if file is writable by effective user |
File.executable?(path) |
path (String) |
Boolean |
Tests if file is executable by effective user |
File.readable_real?(path) |
path (String) |
Boolean |
Tests readability using real user ID |
File.writable_real?(path) |
path (String) |
Boolean |
Tests writability using real user ID |
File.executable_real?(path) |
path (String) |
Boolean |
Tests executability using real user ID |
File.world_readable?(path) |
path (String) |
Integer or nil |
Returns mode if world-readable, nil otherwise |
File.world_writable?(path) |
path (String) |
Integer or nil |
Returns mode if world-writable, nil otherwise |
File Modification Methods
Method | Parameters | Returns | Description |
---|---|---|---|
File.chmod(mode, *paths) |
mode (Integer), paths (String) |
Integer |
Changes file permissions, returns number of files changed |
File.chown(uid, gid, *paths) |
uid (Integer), gid (Integer), paths (String) |
Integer |
Changes file ownership |
File.lchown(uid, gid, *paths) |
uid (Integer), gid (Integer), paths (String) |
Integer |
Changes symlink ownership |
File.umask() |
None | Integer |
Returns current umask |
File.umask(mask) |
mask (Integer) |
Integer |
Sets umask, returns previous value |
File::Stat Permission Methods
Method | Parameters | Returns | Description |
---|---|---|---|
stat.mode |
None | Integer |
Returns full file mode including type and permissions |
stat.readable? |
None | Boolean |
Tests if file is readable |
stat.readable_real? |
None | Boolean |
Tests readability with real user ID |
stat.writable? |
None | Boolean |
Tests if file is writable |
stat.writable_real? |
None | Boolean |
Tests writability with real user ID |
stat.executable? |
None | Boolean |
Tests if file is executable |
stat.executable_real? |
None | Boolean |
Tests executability with real user ID |
stat.world_readable? |
None | Integer or nil |
Returns permissions if world-readable |
stat.world_writable? |
None | Integer or nil |
Returns permissions if world-writable |
Permission Constants
Constant | Value | Description |
---|---|---|
File::RDONLY |
0 | Read-only file access |
File::WRONLY |
1 | Write-only file access |
File::RDWR |
2 | Read-write file access |
File::CREAT |
512 | Create file if it doesn't exist |
File::EXCL |
2048 | Fail if file exists when creating |
File::TRUNC |
1024 | Truncate file to zero length |
File::APPEND |
8 | Append to file |
Common Permission Modes
Mode | Octal | Binary | Description |
---|---|---|---|
--- |
0 | 000 | No permissions |
--x |
1 | 001 | Execute only |
-w- |
2 | 010 | Write only |
-wx |
3 | 011 | Write and execute |
r-- |
4 | 100 | Read only |
r-x |
5 | 101 | Read and execute |
rw- |
6 | 110 | Read and write |
rwx |
7 | 111 | Read, write, and execute |
File Type Constants
Constant | Description |
---|---|
File::FT_FIFO |
Named pipe (FIFO) |
File::FT_CHARACTERSPECIAL |
Character special device |
File::FT_DIRECTORY |
Directory |
File::FT_BLOCKSPECIAL |
Block special device |
File::FT_REGULARFILE |
Regular file |
File::FT_LINK |
Symbolic link |
File::FT_SOCKET |
Socket |
Permission Bitmask Operations
# Extract permission components
mode = File.stat('file.txt').mode
permissions = mode & 0777 # Get permission bits only
owner_perms = (mode & 0700) >> 6 # Owner permissions (0-7)
group_perms = (mode & 0070) >> 3 # Group permissions (0-7)
other_perms = mode & 0007 # Other permissions (0-7)
# Test specific permissions
owner_read = (mode & 0400) != 0 # Owner can read
group_write = (mode & 0020) != 0 # Group can write
other_exec = (mode & 0001) != 0 # Others can execute
# Common permission combinations
mode & 0111 != 0 # Any execute permission
mode & 0222 != 0 # Any write permission
mode & 0444 != 0 # Any read permission
Common Exception Types
Exception | Description |
---|---|
Errno::EACCES |
Permission denied |
Errno::EPERM |
Operation not permitted |
Errno::ENOENT |
No such file or directory |
Errno::EISDIR |
Is a directory |
Errno::ENOTDIR |
Not a directory |
Errno::EROFS |
Read-only file system |