Linux 以原子方式移动目录
我在同一个父目录中有两个目录。调用父目录base和子目录alpha和bravo。我想用bravo替换alpha。最简单的方法是:Linux 以原子方式移动目录,linux,bash,atomic,Linux,Bash,Atomic,我在同一个父目录中有两个目录。调用父目录base和子目录alpha和bravo。我想用bravo替换alpha。最简单的方法是: rm -rf alpha mv bravo alpha mv命令是原子的,但rm-rf不是。在bash中有没有一种简单的方法可以用bravo原子化地替换alpha?如果不是,有没有复杂的方法 增编: 顺便说一句,如果目录在短时间内不存在,这不是一个无法克服的问题。只有一个地方尝试访问alpha,它会在执行任何关键操作之前检查alpha是否存在。如果没有,它将给出一条
rm -rf alpha
mv bravo alpha
mv命令是原子的,但rm-rf不是。在bash中有没有一种简单的方法可以用bravo原子化地替换alpha?如果不是,有没有复杂的方法
增编:
顺便说一句,如果目录在短时间内不存在,这不是一个无法克服的问题。只有一个地方尝试访问alpha,它会在执行任何关键操作之前检查alpha是否存在。如果没有,它将给出一条错误消息。但如果有办法做到这一点,那就太好了。:)也许有某种方法可以直接修改inode,或者其他什么…我不相信有任何原子方法可以做到这一点。您的最佳选择是执行以下操作:
mv alpha delme
mv bravo alpha
rm -rf delme
int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
syscall(SYS_renameat2, dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");
如果你是说两个操作都是原子的,我不这么认为。最接近的是:
mv alpha delta
mv bravo alpha
rm -rf delta
但在阿尔法不存在的地方,仍然会有一个小窗口
为了最大限度地降低在alpha不存在时尝试使用alpha的可能性,您可以(如果您有权限):
这将在执行mv
操作时大大提高流程优先级
如果,正如您在附录中所说,只有一个地方可以检查alpha和it错误,如果它不在那里,您可以立即将该代码更改为not error,但在短时间内重试(对于两次
mv
操作,很容易在亚秒的时间内重试)-除非您非常频繁地更换alpha,否则这些重试应该可以缓解任何问题。即使您直接访问inode,也无法在用户空间中自动交换inode值。如果使用符号链接,您可以这样做:
假设alpha是指向目录alpha_1的符号链接,您希望将符号链接切换到指向alpha_2。以下是切换前的情况:
$ ls -l
lrwxrwxrwx alpha -> alpha_1
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2
要使alpha引用alpha_2,请使用ln-nsf:
$ ln -nsf alpha_2 alpha
$ ls -l
lrwxrwxrwx alpha -> alpha_2
drwxr-xr-x alpha_1
drwxr-xr-x alpha_2
现在,您可以删除旧目录:
$ rm -rf alpha_1
请注意,这实际上并不是一个完全原子化的操作,但它确实发生得非常快,因为“ln”命令同时取消链接,然后立即重新创建符号链接。您可以使用strace验证此行为:
$ strace ln -nsf alpha_2 alpha
...
symlink("alpha_2", "alpha") = -1 EEXIST (File exists)
unlink("alpha") = 0
symlink("alpha_2", "alpha") = 0
...
您可以根据需要重复此过程:例如,当您有新版本时,alpha_3:
$ ln -nsf alpha_3 alpha
$ rm -rf alpha_2
使用一个单独的、有保证的原子操作作为信号量 因此,如果创建和删除文件操作是原子操作: 1) 创建一个名为“信号量”的文件 2) 如果且仅当该操作成功(与现有文件无冲突),则执行该操作(处理alpha或移动目录,具体取决于进程)
3) rm信号。需要记住的是,如果在发生此移动/删除时,进程打开了alpha中的任何文件,则进程将不会注意到,并且当文件关闭并最终删除时,写入的任何数据都将丢失。担心操作的原子性质是毫无意义的。问题是,另一个任务对alpha的访问无论如何都不会是原子的 Oddthinking的信号量方法是唯一的出路
如果无法修改其他任务,则必须确保它未运行,然后再进行替换。为什么不执行以下操作:
rm -rf alpha/*
mv bravo/* alpha/
rm -rf bravo/
这意味着alpha中的所有内容都会被销毁,alpha永远不会被删除,所有内容都会被移动。文档部分对其升级锁定协议进行了详细描述,以控制崩溃后的并发读取、独占写入和回滚。有些想法在这里适用。mv和ln可用于原子操作。我使用ln(1)以原子方式部署web应用程序 替换符号链接的正确方法是使用ln-nsf
ln -nsf <target> <link_name>
最终的解决方案是结合符号链接和重命名方法:
mkdir alpha_real
ln -s alpha_real alpha
# now use "alpha"
mkdir beta_real
ln -s beta_real tmp
# atomically rename "tmp" to "alpha"
# use -T to actually replace "alpha" instead of moving *into* "alpha"
mv -T tmp alpha
当然,访问alpha的应用程序必须能够处理路径中的符号链接变化。这里学习David的解决方案,它是完全原子化的。。。您遇到的唯一问题是
mv
的-T
选项不是POSIX,因此某些POSIX操作系统可能不支持它(FreeBSD、Solaris等)。只需稍加修改,即可将此方法更改为完全原子化,并可移植到所有POSIX操作系统:
mkdir -p tmp/real_dir1 tmp/real_dir2
touch tmp/real_dir1/a tmp/real_dir2/a
# start with ./target_dir pointing to tmp/real_dir1
ln -s tmp/real_dir1 target_dir
# create a symlink named target_dir in tmp, pointing to real_dir2
ln -sf tmp/real_dir2 tmp/target_dir
# atomically mv it into ./ replacing ./target_dir
mv tmp/target_dir ./
例如通过:这应该可以做到:
mkdir bravo_dir alpha_dir
ln -s bravo_dir bravo
ln -s alpha_dir alpha
mv -fT bravo alpha
strace mv-布拉沃阿尔法英尺显示:
rename("bravo", "alpha")
对我来说,这看起来非常原子。也可以使用
unionfs fuse
在某个前缀(Z
)中一次性替换整个内容部分:
#mkdir a b c Z
#触摸a/1 b/2 c/3
#ln-sax
#ln-sby
#unionfs X=RW:Y=RW Z
#shopt-s环球之星
#文件**
a:目录
a/1:空
b:目录
b/2:空的
c:目录
c/3:空
X:指向
Y:指向b的符号链接
Z:目录
Z/1:空
Z/2:空
#ln-sfncy
#文件**/*
a:目录
a/1:空
b:目录
b/2:空的
c:目录
c/3:空
X:指向
X/1:空
Y:指向c的符号链接
Y/3:空
Z:目录
Z/1:空
Z/3:空
#fusermount-uz
#rm-r a b c X Y Z
自Linux 3.15以来,新的重命名2
系统调用可以在同一文件系统上自动交换两个路径。然而,它甚至还没有glibc包装,更不用说coreutils访问它的方式了。所以它看起来像这样:
mv alpha delme
mv bravo alpha
rm -rf delme
int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
syscall(SYS_renameat2, dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");
(当然,您应该进行适当的错误处理等–有关更复杂的renameat2
wrapper,请参阅。)
这就是说,其他人提到的符号链接解决方案既简单又可移植,因此除非bravo
已经存在,并且您必须对其进行原子更新,否则请使用符号链接
2020更新:此系统调用的glibc包装器
int dirfd = open(".../base", O_PATH | O_DIRECTORY | O_CLOEXEC);
renameat2(dirfd, "alpha", dirfd, "bravo", RENAME_EXCHANGE);
close(dirfd);
system("rm -rf alpha");