![옵션 1](https://linux55.com/image/195052/%EC%98%B5%EC%85%98%201.png)
다음과 같은 구조가 있다고 가정해 보겠습니다.
$ mkdir d1
$ mkdir d1/d2
$ touch d1/f1
$ touch d1/d2/f2
$ chmod u-w d1/d2
삭제하려고 하면 d1
쓰기 권한이 없기 때문에 삭제할 수 없습니다 d1/d2
. 그러나 여전히 제거됩니다 d1/f1
.
$ rm -rf d1
rm: cannot remove 'd1/d2/f2': Permission denied
$ ls d1
d2 # f1 has been deleted
원자 도구를 구현할 수 있는 방법이 있나요 rm
? 예를 들어 모든 항목을 삭제할 수 없으면 아무것도 삭제되지 않습니다.
답변1
나는 트리의 하드 링크 백업을 생성하는 방식으로 이 프로그램을 작성할 것입니다. 그런 다음 작업이 실패하면 삭제된 파일을 백업에서 복원합니다. 작업이 성공하면 하드 링크 백업이 삭제됩니다.
물론 이것은 "언제든지 전원 코드를 뽑을 수 있다"는 의미에서 원자적이지 않고 논리적으로 원자적일 뿐입니다. 물론 추가 논리와 시작 시 실행되는 일부 후크를 사용하여 정렬할 수도 있습니다.
두 번의 패스(하나는 권한 확인용, 다른 하나는 삭제용)를 만드는 것은 까다롭습니다. 확장된 속성을 포함하여 모든 권한을 확인하려면 논리가 철저해야 합니다. 예를 들어 이렇게 하면 일반 Unix 권한이 괜찮아 보이 sudo chattr +i file
더라도 file
쓰기 가능한 디렉터리는 삭제할 수 없게 됩니다. "이 파일을 삭제할 수 있습니까?"에 대한 가장 좋은 리트머스 테스트는 실제로 시도해 보는 것입니다.
rsync
이는 하드 링크 기반 백업 및 복구에 대한 개념 증명으로 특정 테스트를 거친 개념적 프로토타입입니다 . 스크립트 이름은 다음과 같습니다 atomic-rm.sh
.
#!/bin/sh
set -eu
if [ $# -ne 1 ] ; then
echo "specify directory to remove"
exit 1
fi
ar_src=$(realpath "$1")
ar_tmp=$(mktemp -d "$(dirname "$ar_src")/tmp-XXXXXX")
if [ $? -ne 0 ] ; then
echo "unable to create temporary directory"
exit 1
fi
cleanup()
{
find "$ar_tmp" -type d -print0 | xargs -0 chmod +w
if ! rm -rf "$ar_tmp" ; then
echo "removal of temporary directory $ar_tmp failed"
exit 2 # 2 indicates dirty failure
fi
}
trap cleanup EXIT
if ! rsync -ar --link-dest="$ar_src" "$ar_src"/ "$ar_tmp"/ ; then
echo "unable to create hard-linked backup of $ar_src in $ar_tmp"
exit 1
fi
if ! rm -rf "$ar_src" ; then
if ! rsync -ar --link-dest="$ar_tmp" "$ar_tmp"/ "$ar_src"/ ; then
echo "removal of $ar_src failed; unfortunately, so did the rollback"
exit 2 # 2 indicates dirty failure
fi
exit 1
fi
exit 0
추가적인 노력을 기울이면 부팅 시 복구 스크립트가 느슨한 임시 디렉터리를 정리하는 데 사용할 수 있는 위치에 일부 정보를 저장할 수 있습니다. 임시 디렉토리는 삭제되는 디렉토리의 형제로 생성되어 동일한 파일 시스템에 있도록 합니다 /tmp
.
삭제가 실패하고 롤백이 수행되면 디렉터리가 엉망이 되어 수정 타임스탬프가 변경되었기 때문에 트리의 정확한 상태를 절대적으로 복원할 수 없습니다.
에서는 디렉토리를 쓰기 가능하게 만들기 위해 디렉토리를 cleanup
순회해야 합니다 . find
그 이유는 디렉터리에 쓸 수 없어 삭제가 실패하는 경우 rsync
해당 디렉터리 권한이 복사되므로 백업 복사본에서도 동일한 방식으로 실패하기 때문입니다.
답변2
옵션 1
한 가지 방법은 트리를 디렉토리로 옮긴 trash
다음 이를 rm
가비지 수집에 사용하는 것입니다. 파일 삭제를 사용한 mv
다음 삭제를 시작합니다 rm
( rm
삭제하지 않고 디렉터리 항목만 삭제합니다. 삭제는 참조 계산 가비지 수집기에 의해 발생합니다).
예를 들어
#!/bin/bash -e
directory_tree_to_remove="$1"
#only works if trash directory is on same file-system (no checks done),
# if not a very expensive copy will be done, followed by an `rm`
# :todo: add checks
#uses gnu `mv`: uses safety feature
#does not do full input error checking
trash_dir="…/trash"
#Atomicly remove tree or file, form its current location
mv -t "$trash_dir" "$directory_tree_to_remove"
#Garbage collect
rm -rf "${trash_dir}/${directory_tree_to_remove}" || echo "error: could not garbage collect. There may be garbage left in "$trash_dir"
옵션 2
현재 수행 중인 작업에 적합한 트랜잭션 파일 시스템이 있을 수 있습니다.
답변3
여기서는 "디렉토리와 해당 내용 전체를 삭제할 수 있는지 여부를 미리 아는 방법"이라는 질문에 답하려고 시도했습니다.
Linux의 일반 사용자가 파일을 삭제하는 것을 방지하는 것(내가 생각할 수 있는 것):
- 비어 있지 않은 디렉터리는 삭제할 수 없으므로 해당 디렉터리에 있는 파일을 먼저 삭제해야 합니다.
- 디렉토리에 대한 쓰기 액세스 또는 검색 액세스가 없으면 파일을 삭제할 수 없습니다(상위 디렉토리에서 링크 해제).
t
파일이나 디렉터리를 모두 소유하지 않은 경우 이 비트가 설정된 디렉터리에서 파일의 링크를 해제할 수 없습니다.a
링크된 파일이나 디렉터리에 (FS_APPEND_FL
) 또는i
(FS_IMMUTABLE_FL
) 플래그가 있으면 파일을 삭제할 수 없습니다./
파일이 nornornor 마운트 지점인 경우.
파일을 삭제할 수 없습니다..
.- 파일이 있는지 모르면 파일을 삭제할 수 없습니다. 디렉토리인 경우와 마찬가지로 쓰기 및 검색 권한은 있지만 읽기 권한은 없습니다.
- 읽기 전용 파일 시스템에 있는 파일은 삭제할 수 없습니다.
apparmor
, 기타selinux
필수 액세스 제어 프레임워크는 물론 사용자 네임스페이스, uid 네임스페이스 등도 방해가 될 수 있습니다.- 모든 파일 시스템 드라이버는 자체 제약 조건을 추가할 수 있습니다. 예를 들어, 어떤 사람들은 삭제할 수 없는 특별한 파일/디렉토리를 좋아
zfs
하거나 갖고 있습니다.nfs
이들 중 일부는 시도해 보지 않으면 스크립트로 쉽게 확인할 수 없습니다.
그러나 위에서 언급한 대부분의 일반적인 상황에는 해결 방법이 있습니다. 예를 들어:
[ -w dir ]
GNU는find -writable
디렉토리가 쓰기 가능한지 확인합니다. 읽기 전용 파일 시스템도 관리해야 합니다. 유사한 읽기/검색 가능.ls -nd
(또는 다양한 호환되지 않는 구현stat
)find -user/-uid
파일 소유자를 확인하세요.lsattr
파일 플래그를 검색합니다.mountpoint -q
파일이 마운트 지점인지 확인하십시오. 또한보십시오findmnt
.
그러나 이를 모두 쉘 스크립트에 넣고 안정적으로 실행하는 것은 매우 까다로울 수 있습니다. 다음은 다음을 사용한 시도입니다 zsh
.
#! /bin/zsh -
file=${1?}
export LC_ALL=C
name=$file:t parent=$file:h
die() {
print -ru2 -- "$@"
exit 1
}
# can't delete / . .. ""
[[ $name = (/|.|..|) ]] && die "Can't delete $file"
[[ -e $file || -L $file ]] || die "Can't tell whether $file exists"
[[ -w $parent ]] || die "Parent dir not writable"
[[ -x $parent ]] || die "Parent dir not searchable"
[[ -k $parent && ! -O $parent && ! -O $file ]] &&
die "Parent has t bit, is not ours, nor is $file"
[[ $(lsattr -d -- $parent 2> /dev/null) = ????(i|?a)* ]] &&
die "parent has a or i file flag"
mountpoint -q -- $file &&
die "$file is a mount point"
# at this point, we should be able to delete non-directory files
[[ -L $file || ! -d $file ]] && exit 0
# same for empty readable dirs
[[ -r $file ]] && ()((! $#)) $file(NF) && exit 0
case $file in
(/*) cd -P -- $file;;
(*) cd -P -- ./$file;; # workaround for -, -1, +1, CDPATH...
esac 2> /dev/null || die "Can't cd into $file"
lsattr -Ra .//. 2> /dev/null |
grep -Eq '^.{4}(i|.a).*//' &&
die "Files with a and/or i file flags seen in $file"
zmodload zsh/system
{
ERRNO=0
non_empty_dirs=({.,**/*}(NDFoN))
# error encountered while getting that list. Takes care of
# non-readable dirs or non-lstat()able mountpoints...
(( ERRNO )) && {
syserror -p "Error encountered while traversing $file: " $ERRNO
exit 1
}
}
for dir ($non_empty_dirs) {
[[ -w $dir ]] || die "non-writable non-empty directory found"
[[ -k $dir && ! -O $dir ]] && ()(($#)) $dir/*(ND^OY1) &&
die "directory with t bit I don't own found with files I don't own inside"
}
mountpoints=(${(f)"$(findmnt -nlo target --submount -T .)"})
set -o extendedglob
for mount ($mountpoints) {
# unescape the \xXX parts:
mount=${mount//(#b)\\(x??)/${(#):-0$match[1]}}
[[ $mount = $PWD/* ]] &&
die "Mount point found under $file"
}
# probably deletable.
exit 0
다음과 같이 사용됩니다:
if that-script "$some_dir_or_file"; then
rm -rf -- "$some_dir_or_file"
# and hope for the best
else
echo probably not fully deletable
fi
일부 극단적인 경우에는 여전히 거짓 긍정 또는 거짓 부정이 나타날 수 있습니다. 물론 검사 시점과 통화 시점 사이에 rm
많은 사항이 변경될 수 있습니다 .
답변4
Katz 답변의 수정된 버전입니다. 원본 나무의 복사본을 만들고( cp -a
), 복사본 삭제를 시도하고, 삭제에 성공하면 원본 나무를 삭제합니다.
- -- : 트리의 크기에 따라 시간이 더 오래 걸릴 수 있으며 디스크 공간도 더 많이 소요될 수 있습니다.
- ++ : 원자 삭제가 불가능하면 원래 트리는 변경되지 않고 그대로 유지됩니다.
#!/bin/sh
if [ $# -ne 1 ] ; then
echo "specify directory to remove"
exit 1
fi
ar_src=$(realpath "$1")
ar_tmp=$(mktemp -d "$(dirname "$ar_src")/tmp-XXXXXX")
if [ $? -ne 0 ] ; then
echo "unable to create temporary directory"
exit 1
fi
if ! cp -a "$1" "$ar_tmp"; then
echo "unable to copy"
exit 1
fi
if rm -rf "$ar_tmp"; then
rm -rf "$ar_src";
fi
exit 0
구현하다:
$ ./atomic-rm2.sh d1/
rm: cannot remove '/tmp/testRMDIR/tmp-4sOBNS/d1/d2/f2': Permission denied
$ tree d1
d1
├── d2
│ └── f2
└── f1