Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safely create a file if and only if it does not exist with Python

Tags:

python

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') 
like image 268
Henry Gomersall Avatar asked Jun 11 '12 11:06

Henry Gomersall


People also ask

How do you create a file in Python if it does not exist?

To create a file if not exist in Python, use the open() function. The open() is a built-in Python function that opens the file and returns it as a file object. The open() takes the file path and the mode as input and returns the file object as output.

Which command create an import file if file does not exist?

Using a Write mode “w” or “w+” will create a file if not exists in Python.


2 Answers

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!") 
like image 153
me_and Avatar answered Oct 12 '22 01:10

me_and


For reference, Python 3.3 implements a new 'x' mode in the open() function to cover this use-case (create only, fail if file exists). Note that the 'x' mode is specified on its own. Using 'wx' results in a ValueError as the 'w' is redundant (the only thing you can do if the call succeeds is write to the file anyway; it can't have existed if the call succeeds):

>>> f1 = open('new_binary_file', 'xb') >>> f2 = open('new_text_file', 'x') 

For Python 3.2 and below (including Python 2.x) please refer to the accepted answer.

like image 35
Dave Jones Avatar answered Oct 12 '22 03:10

Dave Jones