Safely create a file if and only if it does not exist with Python
python
Solution
Edit: See also Dave Jones' answer: from Python 3.3, you can use the `x` flag to `open()` to provide this function.
Original answer below
Yes, but not using Python's standard `open()` call. You'll need to use `os.open()` instead, which allows you to specify flags to the underlying C code.
In particular, you want to use `O_CREAT | O_EXCL`. From the man page for `open(2)` under `O_EXCL` on my Unix system:
Ensure that this call creates the file: if this flag is specified in conjunction with `O_CREAT`, and pathname already exists, then `open()` will fail. The behavior of `O_EXCL` is undefined if `O_CREAT` is not specified.
When these two flags are specified, symbolic links are not followed: if pathname is a symbolic link, then `open()` fails regardless of where the symbolic link points to.
`O_EXCL` is only supported on NFS when using NFSv3 or later on kernel 2.6 or later. In environments where NFS `O_EXCL` support is not provided, programs that rely on it for performing locking tasks will contain a race condition.
So it's not perfect, but AFAIK it's the closest you can get to avoiding this race condition.
Edit: the other rules of using `os.open()` instead of `open()` still apply. In particular, if you want use the returned file descriptor for reading or writing, you'll need one of the `O_RDONLY`, `O_WRONLY` or `O_RDWR` flags as well.
All the `O_*` flags are in Python's `os` module, so you'll need to `import os` and use `os.O_CREAT` etc.
Example:
import os
import errno
flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
try:
file_handle = os.open('filename', flags)
except OSError as e:
if e.errno == errno.EEXIST: # Failed as the file already exists.
pass
else: # Something unexpected went wrong so reraise the exception.
raise
else: # No exception, so the file must have been created successfully.
with os.fdopen(file_handle, 'w') as file_obj:
# Using `os.fdopen` converts the handle to an object that acts like a
# regular Python file object, and the `with` context manager means the
# file will be automatically closed when we're done with it.
file_obj.write("Look, ma, I'm writing to a new file!")
Problem
I wish to write to a file based on whether that file already exists or not, only writing if it doesn't already exist (in practice, I wish to keep trying files until I find one that doesn't exist). The following code shows a way in which a potentially attacker could insert a symlink, as suggested in this post in between a test for the file and the file being written. If the code is run with high enough permissions, this could overwrite an arbitrary file. Is there a way to solve this problem? ``` import os import errno file_to_be_attacked = 'important_file' with open(file_to_be_attacked, 'w') as f: f.write('Some important content!\n') test_file = 'testfile' try: with open(test_file) as f: pass except IOError, e: # Symlink created here os.symlink(file_to_be_attacked, test_file) if e.errno != errno.ENOENT: raise else: with open(test_file, 'w') as f: f.write('Hello, kthxbye!\n') ```