Skip to content

use more FDs, avoid race conditions on active fs#4043

Merged
ThomasWaldmann merged 12 commits intoborgbackup:masterfrom
ThomasWaldmann:use-more-fds
Feb 22, 2019
Merged

use more FDs, avoid race conditions on active fs#4043
ThomasWaldmann merged 12 commits intoborgbackup:masterfrom
ThomasWaldmann:use-more-fds

Conversation

@ThomasWaldmann
Copy link
Copy Markdown
Member

@ThomasWaldmann ThomasWaldmann commented Aug 12, 2018

guess this is mostly done, fixes issues mentioned in #908 and #1038.

@ThomasWaldmann ThomasWaldmann force-pushed the use-more-fds branch 2 times, most recently from 9879189 to 0bdeaf4 Compare August 13, 2018 00:51
@codecov-io
Copy link
Copy Markdown

codecov-io commented Aug 13, 2018

Codecov Report

Merging #4043 into master will increase coverage by 0.1%.
The diff coverage is 86.09%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master    #4043     +/-   ##
=========================================
+ Coverage   84.29%   84.39%   +0.1%     
=========================================
  Files          37       37             
  Lines        9430     9467     +37     
  Branches     1572     1573      +1     
=========================================
+ Hits         7949     7990     +41     
+ Misses       1036     1034      -2     
+ Partials      445      443      -2
Impacted Files Coverage Δ
src/borg/archiver.py 81.93% <80%> (+0.38%) ⬆️
src/borg/helpers/fs.py 91.11% <81.25%> (-3.39%) ⬇️
src/borg/archive.py 84.06% <93.75%> (+0.34%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 091bd2b...23eeded. Read the comment docs.

@ThomasWaldmann
Copy link
Copy Markdown
Member Author

this is how a borg create (py36) with the code of this PR currently looks like:

# 6 is the fd of the "docs" directory, now backing up "3rd_party" dir and contents:

newfstatat(6, "3rd_party", {st_mode=S_IFDIR|0775, st_size=4096, ...}, AT_SYMLINK_NOFOLLOW) = 0
openat(6, "3rd_party", O_RDONLY|O_NOFOLLOW|O_NOATIME|O_CLOEXEC|O_DIRECTORY) = 7
fstat(7, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
flistxattr(7, "", 4096)                 = 0
ioctl(7, FS_IOC_GETFLAGS, 0x7ffe824aff1c) = 0
fgetxattr(7, "system.posix_acl_access", NULL, 0) = -1 ENODATA (No data available)
fgetxattr(7, "system.posix_acl_default", NULL, 0) = -1 ENODATA (No data available)
openat(AT_FDCWD, "docs/3rd_party", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 9
fstat(9, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
getdents(9, /* 6 entries */, 32768)     = 160
getdents(9, /* 0 entries */, 32768)     = 0
close(9)                                = 0

newfstatat(7, "README", {st_mode=S_IFREG|0664, st_size=215, ...}, AT_SYMLINK_NOFOLLOW) = 0
openat(7, "README", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_NOATIME|O_CLOEXEC) = 9
fstat(9, {st_mode=S_IFREG|0664, st_size=215, ...}) = 0
read(9, "Here we store 3rd party document"..., 8388608) = 215
fadvise64(9, 0, 0, POSIX_FADV_DONTNEED) = 0
read(9, "", 8388393)                    = 0
fadvise64(9, 0, 0, POSIX_FADV_DONTNEED) = 0
flistxattr(9, "", 4096)                 = 0
ioctl(9, FS_IOC_GETFLAGS, 0x7ffe824afc7c) = 0
fgetxattr(9, "system.posix_acl_access", NULL, 0) = -1 ENODATA (No data available)
fgetxattr(9, "system.posix_acl_default", NULL, 0) = -1 ENODATA (No data available)
close(9)                                = 0

# (... all the stuff in there ...)

close(7)                                = 0

@ThomasWaldmann
Copy link
Copy Markdown
Member Author

ThomasWaldmann commented Aug 15, 2018

same with py37 (using the new fd-based scandir()):

newfstatat(6, "3rd_party", {st_mode=S_IFDIR|0775, st_size=4096, ...}, AT_SYMLINK_NOFOLLOW) = 0
openat(6, "3rd_party", O_RDONLY|O_NOFOLLOW|O_NOATIME|O_CLOEXEC|O_DIRECTORY) = 7
fstat(7, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
flistxattr(7, "", 4096)                 = 0
ioctl(7, FS_IOC_GETFLAGS, 0x7fff0a3e595c) = 0
fgetxattr(7, "system.posix_acl_access", NULL, 0) = -1 ENODATA (No data available)
fgetxattr(7, "system.posix_acl_default", NULL, 0) = -1 ENODATA (No data available)
fcntl(7, F_DUPFD_CLOEXEC, 0)            = 9
fstat(9, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
fcntl(9, F_GETFL)                       = 0x78000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_NOATIME|O_DIRECTORY)
fcntl(9, F_SETFD, FD_CLOEXEC)           = 0
getdents(9, /* 6 entries */, 32768)     = 160
getdents(9, /* 0 entries */, 32768)     = 0
lseek(9, 0, SEEK_SET)                   = 0
close(9)                                = 0

newfstatat(7, "README", {st_mode=S_IFREG|0664, st_size=215, ...}, AT_SYMLINK_NOFOLLOW) = 0
openat(7, "README", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_NOATIME|O_CLOEXEC) = 9
fstat(9, {st_mode=S_IFREG|0664, st_size=215, ...}) = 0
read(9, "Here we store 3rd party document"..., 8388608) = 215
fadvise64(9, 0, 0, POSIX_FADV_DONTNEED) = 0
read(9, "", 8388393)                    = 0
fadvise64(9, 0, 0, POSIX_FADV_DONTNEED) = 0
flistxattr(9, "", 4096)                 = 0
ioctl(9, FS_IOC_GETFLAGS, 0x7fff0a3e56dc) = 0
fgetxattr(9, "system.posix_acl_access", NULL, 0) = -1 ENODATA (No data available)
fgetxattr(9, "system.posix_acl_default", NULL, 0) = -1 ENODATA (No data available)
close(9)                                = 0

st param was only given at the root paths of the recursion.
we can just drop that and make the code simpler.
always open the file and then do all operations with the fd:
- fstat
- read
- get xattrs, acls, bsdflags
also:
- add and use OsOpen context manager
- add O_NONBLOCK, O_NOFOLLOW, O_NOCTTY (inspired by gnu tar)
we'll add/remove some args soon, so many pos args would be just bad.
races via changing path components can be avoided by opening the
parent directory and using parent_fd + file_name combination with
*at style functions to access the directories' contents.
if scandir does not get a path, it can't prefix it in front of the
filename in the direntries it returns, so dirent.path == dirent.name.

thus, we just only use dirent.name and construct the full path.
acl_get:

remove assumption that having an FD means it is a regular file, we try
to use FDs a much as possible.

only get the default acl for directories - other fs objects are not
expected to have a default acl.

the path needs to be encoded also for the case when we have an fd,
it is needed to get the default acl for directories.

also: micro-opt: encode path later, not needed for ISLNK check.

acl_set:

remove the "if False" branch, it is the same here: the fd-based api
only supports access ACLs, but not default ACLs, so we always need
to use the path-based api here.
for fd-based operations, we would have to open the file, but for
char / block devices this has unwanted effects, even if we do not
read from the device.

thus, we use path (or dir_fd + name) based ops here.
on linux, acls are based on xattrs, so do these closeby:

1. listxattr -> keys (without acl related keys)
2. for all keys: getxattr
3. acl-related getxattr by acl library
scenario:

- x is a regular file
- borg does stat on x: is a regular file
- so borg dispatches to process_file
- attack: x gets replaced by a symlink (mv symlink x)
- in process_file, borg opens x and must not follow the symlink nor
  continue processing as a normal file, but rather error in open()
  due to NOFOLLOW.
@ThomasWaldmann
Copy link
Copy Markdown
Member Author

ThomasWaldmann commented Feb 18, 2019

@verygreen ah, right. guess one can not change the file type of an existing inode (except with a sector editor maybe? :-) ).

is there any way / chance to kill the old inode and get a new one (with different file type) with the same inode number?

if not, considering this code is called once per file (fs item), I'll remove the file type check again.

@ThomasWaldmann
Copy link
Copy Markdown
Member Author

(we also need to consider fs with synthetic inode numbers, like network fs for that)

@verygreen
Copy link
Copy Markdown
Contributor

yes, technically inode numbers could be resused, so if you want to be 100% sure this did not happen, you need to look at inode number and generation, but generation apparently is not exposed outside of the kernel for whatever reason.

That said conditions of inode reuse in relatively short time should still be very rare even on synthetic fses (esp. there since there they typically just do like a constantly increasing counter).

Here is a real world example of inode reuse: GoogleCloudPlatform/gcsfuse#57

On ext3/4 the fs tries to avoid reusing recently deleted inodes unless you really don't have any other left. Probably many other filesystems do this too.

we must avoid a handler processing a fs item of wrong file type,
so check if it has changed.
@ThomasWaldmann ThomasWaldmann changed the title WIP: use more FDs use more FDs, avoid race conditions on active fs Feb 20, 2019
@ThomasWaldmann ThomasWaldmann merged commit d644323 into borgbackup:master Feb 22, 2019
@ThomasWaldmann ThomasWaldmann deleted the use-more-fds branch February 22, 2019 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants