ZetCode

Python os.umask Function

Last modified April 11, 2025

This comprehensive guide explores Python's os.umask function, which sets the file mode creation mask. We'll cover permission bits, mask calculation, and practical file creation examples.

Basic Definitions

The os.umask function sets the current numeric umask value and returns the previous value. The umask determines default file permissions.

The umask is subtracted from the default permissions (usually 666 for files, 777 for directories) to determine actual permissions. It uses octal notation.

Getting Current Umask Value

To retrieve the current umask without changing it, call os.umask with the current value. This preserves the existing mask while reading it.

get_umask.py
import os

# Get current umask without changing it
current_umask = os.umask(0)
os.umask(current_umask)  # Restore original umask

print(f"Current umask: {oct(current_umask)}")
print(f"Interpreted as: {current_umask:03o}")

# Typical default umask values
print("\nCommon umask values:")
print("022 - Owner has full, group/others read/execute")
print("002 - Owner/group have full, others read/execute")
print("077 - Only owner has full access")

This example shows how to safely read the current umask value. The octal format represents which permission bits will be disabled by default.

The umask is process-specific and affects all subsequent file/directory creations within that process.

Setting a New Umask Value

To change the umask, pass the new value to os.umask. The function returns the previous umask value. Use octal notation for clarity.

set_umask.py
import os

# Save current umask
original_umask = os.umask(0)
os.umask(original_umask)  # Restore immediately

print(f"Original umask: {oct(original_umask)}")

# Set restrictive umask (owner only)
new_umask = 0o077
old_umask = os.umask(new_umask)
print(f"Changed umask from {oct(old_umask)} to {oct(new_umask)}")

# Create file with new umask
with open("restricted_file.txt", "w") as f:
    f.write("Secret content")

# Restore original umask
os.umask(original_umask)
print(f"Restored original umask: {oct(original_umask)}")

This demonstrates changing the umask temporarily to create a file with restrictive permissions, then restoring the original umask.

The umask affects all subsequent file operations until changed again or the process ends.

Understanding Umask Calculation

The umask works by subtracting permission bits from default modes. For files, default is 666 (rw-rw-rw-), for directories 777 (rwxrwxrwx).

umask_calculation.py
import os
import stat

def show_permissions(path):
    mode = os.stat(path).st_mode
    print(f"{path}: {oct(mode & 0o777)}")

# Default umask (usually 022)
default_umask = 0o022
os.umask(default_umask)

# Create file and directory
with open("normal_file.txt", "w") as f:
    f.write("Test content")
os.mkdir("normal_dir")

show_permissions("normal_file.txt")  # Should be 644 (666 - 022)
show_permissions("normal_dir")       # Should be 755 (777 - 022)

# Change umask to 077 (restrictive)
os.umask(0o077)
with open("private_file.txt", "w") as f:
    f.write("Private content")
os.mkdir("private_dir")

show_permissions("private_file.txt")  # Should be 600 (666 - 077)
show_permissions("private_dir")       # Should be 700 (777 - 077)

This example shows how different umask values affect file and directory permissions. The actual permissions are calculated by bitwise AND with the umask's complement.

The umask doesn't grant permissions - it only restricts the default ones.

Temporary Umask Context

For safe umask changes, use a context manager that automatically restores the original umask. This prevents accidental permission leaks.

umask_context.py
import os
from contextlib import contextmanager

@contextmanager
def temp_umask(new_umask):
    old_umask = os.umask(new_umask)
    try:
        yield
    finally:
        os.umask(old_umask)

# Original umask
print(f"Start umask: {oct(os.umask(0))}")
os.umask(0o022)  # Reset to common default

# Use restrictive umask temporarily
with temp_umask(0o077):
    print(f"Inside context umask: {oct(os.umask(0))}")
    os.umask(0o077)  # Restore within context
    with open("temp_secure.txt", "w") as f:
        f.write("Temporary secure file")

# Verify umask restored
print(f"After context umask: {oct(os.umask(0))}")
os.umask(0o022)  # Clean up

The context manager ensures the umask is always restored, even if an exception occurs. This is safer than manual umask management.

This pattern is especially useful in libraries where you can't predict how your code will be used.

Umask with os.makedirs

When creating directories recursively with os.makedirs, the umask affects all created directories. This example demonstrates the behavior.

makedirs_umask.py
import os
import stat

def show_tree_permissions(root):
    for dirpath, dirnames, filenames in os.walk(root):
        print(f"\nDirectory: {dirpath}")
        mode = os.stat(dirpath).st_mode
        print(f"Permissions: {oct(mode & 0o777)}")
        for f in filenames:
            path = os.path.join(dirpath, f)
            mode = os.stat(path).st_mode
            print(f"File {f}: {oct(mode & 0o777)}")

# Set restrictive umask
os.umask(0o077)

# Create directory tree
try:
    os.makedirs("secure_tree/sub1/sub2")
    with open("secure_tree/file1.txt", "w") as f:
        f.write("Top level")
    with open("secure_tree/sub1/file2.txt", "w") as f:
        f.write("Sub level")
    
    show_tree_permissions("secure_tree")
finally:
    # Clean up
    os.umask(0o022)
    # Remove test directories
    import shutil
    shutil.rmtree("secure_tree", ignore_errors=True)

All directories and files created under the restrictive umask inherit the limited permissions. The umask affects the entire operation.

This shows how umask provides a way to enforce permission policies throughout an application.

Platform-Specific Umask Behavior

Umask behavior differs slightly between Unix and Windows systems. This example demonstrates the differences and how to handle them.

platform_umask.py
import os
import sys
import stat

def create_test_file():
    test_file = "umask_test.txt"
    with open(test_file, "w") as f:
        f.write("Platform test")
    return test_file

# Get current umask
current_umask = os.umask(0)
os.umask(current_umask)

print(f"Platform: {sys.platform}")
print(f"Current umask: {oct(current_umask)}")

# Windows specific behavior
if sys.platform == "win32":
    print("\nWindows notes:")
    print("- Umask affects only the execute bit")
    print("- Read/write permissions not fully enforced")
    print("- Default umask is usually 0o022")
    
    # Windows ignores some umask bits
    os.umask(0o077)  # Try to set restrictive
    test_file = create_test_file()
    mode = os.stat(test_file).st_mode
    print(f"Created file permissions: {oct(mode & 0o777)}")
    os.unlink(test_file)
else:
    # Unix behavior
    print("\nUnix notes:")
    print("- Umask fully affects all permission bits")
    print("- Strict enforcement of permissions")
    
    os.umask(0o077)  # Restrictive umask
    test_file = create_test_file()
    mode = os.stat(test_file).st_mode
    print(f"Created file permissions: {oct(mode & 0o777)}")
    os.unlink(test_file)

# Restore original umask
os.umask(current_umask)

On Unix systems, the umask fully controls permissions. Windows has limited support, mainly affecting the execute bit.

When writing cross-platform code, don't rely on umask alone for security. Add explicit permission checks.

Security Considerations

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.