mirror of https://github.com/borgbackup/borg.git
206 lines
6.0 KiB
Python
206 lines
6.0 KiB
Python
import errno
|
|
import os
|
|
|
|
"""
|
|
platform base module
|
|
====================
|
|
|
|
Contains platform API implementations based on what Python itself provides. More specific
|
|
APIs are stubs in this module.
|
|
|
|
When functions in this module use platform APIs themselves they access the public
|
|
platform API: that way platform APIs provided by the platform-specific support module
|
|
are correctly composed into the base functionality.
|
|
"""
|
|
|
|
API_VERSION = '1.1_01'
|
|
|
|
fdatasync = getattr(os, 'fdatasync', os.fsync)
|
|
|
|
|
|
def acl_get(path, item, st, numeric_owner=False):
|
|
"""
|
|
Saves ACL Entries
|
|
|
|
If `numeric_owner` is True the user/group field is not preserved only uid/gid
|
|
"""
|
|
|
|
|
|
def acl_set(path, item, numeric_owner=False):
|
|
"""
|
|
Restore ACL Entries
|
|
|
|
If `numeric_owner` is True the stored uid/gid is used instead
|
|
of the user/group names
|
|
"""
|
|
|
|
|
|
try:
|
|
from os import lchflags
|
|
|
|
def set_flags(path, bsd_flags, fd=None):
|
|
lchflags(path, bsd_flags)
|
|
except ImportError:
|
|
def set_flags(path, bsd_flags, fd=None):
|
|
pass
|
|
|
|
|
|
def get_flags(path, st):
|
|
"""Return BSD-style file flags for path or stat without following symlinks."""
|
|
return getattr(st, 'st_flags', 0)
|
|
|
|
|
|
def sync_dir(path):
|
|
fd = os.open(path, os.O_RDONLY)
|
|
try:
|
|
os.fsync(fd)
|
|
except OSError as os_error:
|
|
# Some network filesystems don't support this and fail with EINVAL.
|
|
# Other error codes (e.g. EIO) shouldn't be silenced.
|
|
if os_error.errno != errno.EINVAL:
|
|
raise
|
|
finally:
|
|
os.close(fd)
|
|
|
|
|
|
def safe_fadvise(fd, offset, len, advice):
|
|
if hasattr(os, 'posix_fadvise'):
|
|
try:
|
|
os.posix_fadvise(fd, offset, len, advice)
|
|
except OSError:
|
|
# usually, posix_fadvise can't fail for us, but there seem to
|
|
# be failures when running borg under docker on ARM, likely due
|
|
# to a bug outside of borg.
|
|
# also, there is a python wrapper bug, always giving errno = 0.
|
|
# https://github.com/borgbackup/borg/issues/2095
|
|
# as this call is not critical for correct function (just to
|
|
# optimize cache usage), we ignore these errors.
|
|
pass
|
|
|
|
|
|
class SyncFile:
|
|
"""
|
|
A file class that is supposed to enable write ordering (one way or another) and data durability after close().
|
|
|
|
The degree to which either is possible varies with operating system, file system and hardware.
|
|
|
|
This fallback implements a naive and slow way of doing this. On some operating systems it can't actually
|
|
guarantee any of the above, since fsync() doesn't guarantee it. Furthermore it may not be possible at all
|
|
to satisfy the above guarantees on some hardware or operating systems. In these cases we hope that the thorough
|
|
checksumming implemented catches any corrupted data due to misordered, delayed or partial writes.
|
|
|
|
Note that POSIX doesn't specify *anything* about power failures (or similar failures). A system that
|
|
routinely loses files or corrupts file on power loss is POSIX compliant.
|
|
|
|
TODO: Use F_FULLSYNC on OSX.
|
|
TODO: A Windows implementation should use CreateFile with FILE_FLAG_WRITE_THROUGH.
|
|
"""
|
|
|
|
def __init__(self, path, binary=False):
|
|
mode = 'xb' if binary else 'x'
|
|
self.fd = open(path, mode)
|
|
self.fileno = self.fd.fileno()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.close()
|
|
|
|
def write(self, data):
|
|
self.fd.write(data)
|
|
|
|
def sync(self):
|
|
"""
|
|
Synchronize file contents. Everything written prior to sync() must become durable before anything written
|
|
after sync().
|
|
"""
|
|
from .. import platform
|
|
self.fd.flush()
|
|
platform.fdatasync(self.fileno)
|
|
# tell the OS that it does not need to cache what we just wrote,
|
|
# avoids spoiling the cache for the OS and other processes.
|
|
safe_fadvise(self.fileno, 0, 0, os.POSIX_FADV_DONTNEED)
|
|
|
|
def close(self):
|
|
"""sync() and close."""
|
|
from .. import platform
|
|
dirname = None
|
|
try:
|
|
dirname = os.path.dirname(self.fd.name)
|
|
self.sync()
|
|
finally:
|
|
self.fd.close()
|
|
if dirname:
|
|
platform.sync_dir(dirname)
|
|
|
|
|
|
class SaveFile:
|
|
"""
|
|
Update file contents atomically.
|
|
|
|
Must be used as a context manager (defining the scope of the transaction).
|
|
|
|
On a journaling file system the file contents are always updated
|
|
atomically and won't become corrupted, even on power failures or
|
|
crashes (for caveats see SyncFile).
|
|
"""
|
|
|
|
SUFFIX = '.tmp'
|
|
|
|
def __init__(self, path, binary=False):
|
|
self.binary = binary
|
|
self.path = path
|
|
self.tmppath = self.path + self.SUFFIX
|
|
|
|
def __enter__(self):
|
|
from .. import platform
|
|
try:
|
|
os.unlink(self.tmppath)
|
|
except FileNotFoundError:
|
|
pass
|
|
self.fd = platform.SyncFile(self.tmppath, self.binary)
|
|
return self.fd
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
from .. import platform
|
|
self.fd.close()
|
|
if exc_type is not None:
|
|
os.unlink(self.tmppath)
|
|
return
|
|
os.replace(self.tmppath, self.path)
|
|
platform.sync_dir(os.path.dirname(self.path))
|
|
|
|
|
|
def swidth(s):
|
|
"""terminal output width of string <s>
|
|
|
|
For western scripts, this is just len(s), but for cjk glyphs, 2 cells are used.
|
|
"""
|
|
return len(s)
|
|
|
|
|
|
def umount(mountpoint):
|
|
"""un-mount the FUSE filesystem mounted at <mountpoint>"""
|
|
return 0 # dummy, see also posix module
|
|
|
|
|
|
def get_process_id():
|
|
"""
|
|
Return identification tuple (hostname, pid, thread_id) for 'us'. If this is a FUSE process, then the PID will be
|
|
that of the parent, not the forked FUSE child.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def process_alive(host, pid, thread):
|
|
"""
|
|
Check if the (host, pid, thread_id) combination corresponds to a potentially alive process.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def local_pid_alive(pid):
|
|
"""Return whether *pid* is alive."""
|
|
raise NotImplementedError
|