Làm thế nào để sao chép một tập tin giao dịch?


9

Tôi muốn sao chép một tệp từ A sang B, có thể trên các hệ thống tệp khác nhau.

Có một số yêu cầu bổ sung:

  1. Bản sao là tất cả hoặc không có gì, không có tệp B bị hỏng hoặc bị hỏng một phần;
  2. Không ghi đè lên tệp B hiện có;
  3. Không cạnh tranh với việc thực hiện đồng thời cùng một lệnh, nhiều nhất một người có thể thành công.

Tôi nghĩ rằng điều này được gần gũi:

cp A B.part && \
ln B B.part && \
rm B.part

Nhưng 3. bị vi phạm bởi cp không bị lỗi nếu B.part tồn tại (ngay cả với cờ -n). Sau đó 1. có thể thất bại nếu quá trình khác 'thắng' cp và tệp được liên kết vào vị trí không đầy đủ. B.part cũng có thể là một tệp không liên quan, nhưng tôi rất vui khi thất bại mà không thử các tên ẩn khác trong trường hợp đó.

Tôi nghĩ bash noclobber giúp, điều này có hoạt động đầy đủ không? Có cách nào để có được mà không cần yêu cầu phiên bản bash không?

#!/usr/bin/env bash
set -o noclobber
cat A > B.part && \
ln B.part B && \
rm B.part

Theo dõi, tôi biết một số hệ thống tập tin sẽ thất bại tại đây (NFS). Có cách nào để phát hiện các hệ thống tập tin như vậy?

Một số câu hỏi khác có liên quan nhưng không hoàn toàn giống nhau:

Xấp xỉ di chuyển nguyên tử trên các hệ thống tập tin?

Là mv nguyên tử trên fs của tôi?

Có cách nào để di chuyển nguyên bản tệp và thư mục từ tempfs sang phân vùng ext4 trên eMMC

https://rcrowley.org/2010/01/06/things-unix-can-do-atom Về.html


2
Bạn chỉ quan tâm đến việc thực thi đồng thời cùng một lệnh (nghĩa là có thể khóa trong công cụ của bạn đủ) hay về sự can thiệp bên ngoài khác với các tệp không?
Michael Homer

3
"Giao dịch" có thể tốt hơn
muru

1
@MichaelHomer trong công cụ là đủ tốt, tôi nghĩ bên ngoài sẽ làm mọi thứ rất khó khăn! Nếu có thể với khóa tập tin mặc dù ...
Evan Benn

1
@marcelm mvsẽ ghi đè lên một tệp hiện có B. mv -nsẽ không thông báo rằng nó đã thất bại. ln(1)( rename(2)) sẽ thất bại nếu B đã tồn tại.
Evan Benn

1
@EvanBenn Điểm tốt! Tôi nên đọc yêu cầu của bạn tốt hơn. (Tôi có xu hướng cần cập nhật nguyên tử của một mục tiêu hiện có và tôi đã trả lời với ý nghĩ đó)
marcelm

Câu trả lời:


11

rsynclàm công việc này Một tệp tạm thời được O_EXCLtạo theo mặc định (chỉ bị vô hiệu hóa nếu bạn sử dụng --inplace) và sau đó renamedqua tệp đích. Sử dụng --ignore-existingđể không ghi đè B nếu nó tồn tại.

Trong thực tế, tôi chưa bao giờ gặp bất kỳ vấn đề nào với điều này trên các mount ext4, zfs hoặc thậm chí NFS.


rsync có thể làm điều này độc đáo, nhưng trang người đàn ông cực kỳ phức tạp làm tôi sợ. các tùy chọn ngụ ý các tùy chọn khác, không tương thích với nhau, v.v.
Evan Benn

Rsync không giúp với yêu cầu số 3, theo như tôi có thể nói. Tuy nhiên, đây là một công cụ tuyệt vời và bạn không nên né tránh việc đọc trang cá nhân. Bạn cũng có thể thử github.com/tldr-pages/tldr/blob/master/pages/common/rsync.md hoặc cheat.sh/rsync . (TLDR và cheat là hai dự án khác nhau mà mục tiêu là giúp giải quyết những vấn đề bạn nói, tức là, "người đàn ông trang là TL; DR"; nhiều lệnh phổ biến được hỗ trợ, và bạn sẽ thấy các tập quán phổ biến nhất được hiển thị..
Sitaram

@EvanBenn rsync là một công cụ tuyệt vời và đáng để học hỏi! Trang người đàn ông rất phức tạp vì nó rất linh hoạt. Đừng sợ hãi :)
Josh

@sitaram, # 3 có thể được giải quyết bằng tệp pid. Một kịch bản nhỏ như trong câu trả lời ở đây .
Robert Riedl

2
Đây là câu trả lời tốt nhất. Rupync là tiêu chuẩn công nghiệp để chuyển tập tin nguyên tử và trong các cấu hình khác nhau có thể đáp ứng tất cả các yêu cầu của bạn.
wKavey

4

Cảm ơn, cám dỗ để chấp nhận câu trả lời ngắn gọn này. Bất kỳ bình luận về các hệ thống tập tin tinh ranh như NFS?
Evan Benn

@EvanBenn, tôi muốn nói thêm rằng tôi không chắc liệu NFS có làm phiền bạn ở đây theo một cách nào đó không, nhưng tôi đã quên.
ilkkachu

4

Bạn đã hỏi về NFS. Loại mã này có khả năng bị phá vỡ theo NFS, vì kiểm tra noclobberliên quan đến hai hoạt động NFS riêng biệt (kiểm tra nếu tệp tồn tại, tạo tệp mới) và hai quy trình từ hai máy khách NFS riêng biệt có thể rơi vào tình trạng chạy đua khi cả hai đều thành công ( cả hai đều xác minh rằng B.partchưa tồn tại, sau đó cả hai tiến hành tạo thành công, kết quả là họ ghi đè lên nhau.)

Không thực sự phải kiểm tra chung xem liệu hệ thống tập tin bạn đang viết có hỗ trợ một cái gì đó như noclobbernguyên tử hay không. Bạn có thể kiểm tra loại hệ thống tập tin, cho dù đó là NFS, nhưng đó sẽ là một heuristic và không nhất thiết phải là một sự đảm bảo. Các hệ thống tập tin như SMB / CIFS (Samba) có thể gặp phải vấn đề tương tự. Các hệ thống tập tin phơi bày thông qua FUSE có thể có hoặc không hoạt động chính xác, nhưng điều đó chủ yếu phụ thuộc vào việc thực hiện.


Một cách tiếp cận có thể tốt hơn là tránh va chạm trong B.partbước này, bằng cách sử dụng tên tệp duy nhất (thông qua hợp tác với các đại lý khác) để bạn không cần phải phụ thuộc noclobber. Chẳng hạn, bạn có thể bao gồm, như một phần của tên tệp, tên máy chủ của bạn, PID và dấu thời gian (+ có thể là một số ngẫu nhiên.) Vì sẽ có một quy trình duy nhất chạy theo một PID cụ thể tại một máy chủ tại bất kỳ thời điểm nào, điều này nên đảm bảo tính độc đáo.

Vì vậy, một trong những:

test -f B && continue  # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
# Maybe check for existance of B again, remove
# the temporary file and bail out in that case.
mv B.part."$unique" B
# mv (rename) should always succeed, overwrite a
# previously copied B if one exists.

Hoặc là:

test -f B && continue  # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
if ln B.part."$unique" B ; then
    echo "Success creating B"
else
    echo "Failed creating B, already existed"
fi
# Both cases require cleanup.
rm B.part."$unique"

Vì vậy, nếu bạn có điều kiện chạy đua giữa hai tác nhân, cả hai sẽ tiến hành hoạt động, nhưng thao tác cuối cùng sẽ là nguyên tử, do đó, B tồn tại với bản sao đầy đủ của A hoặc B không tồn tại.

Bạn có thể giảm kích thước của cuộc đua bằng cách kiểm tra lại sau khi sao chép và trước khi mvhoặc lnhoạt động, nhưng vẫn còn một điều kiện cuộc đua nhỏ ở đó. Nhưng, bất kể điều kiện cuộc đua, nội dung của B phải nhất quán, giả sử cả hai quá trình đang cố gắng tạo nó từ A (hoặc bản sao từ tệp hợp lệ làm gốc.)

Lưu ý rằng trong tình huống đầu tiên mv, khi một chủng tộc tồn tại, quá trình cuối cùng là người chiến thắng, vì đổi tên (2) về cơ bản sẽ thay thế một tệp hiện có:

Nếu newpath đã tồn tại, nó sẽ được thay thế về mặt nguyên tử, do đó không có điểm nào mà quá trình khác cố gắng truy cập newpath sẽ thấy nó bị thiếu. [...]

Nếu newpath tồn tại nhưng hoạt động không thành công vì một số lý do, rename()đảm bảo để lại một trường hợp của newpath tại chỗ.

Vì vậy, rất có thể các quy trình tiêu thụ B tại thời điểm đó có thể thấy các phiên bản khác nhau của nó (các nút khác nhau) trong quá trình này. Nếu người viết chỉ cố gắng sao chép cùng một nội dung và người đọc chỉ đơn giản là tiêu thụ nội dung của tệp, điều đó có thể ổn, nếu họ nhận được các nút khác nhau cho các tệp có cùng nội dung, họ sẽ rất vui.

Cách tiếp cận thứ hai sử dụng liên kết cứng có vẻ tốt hơn, nhưng tôi nhớ lại việc thực hiện các thử nghiệm với các liên kết cứng trong một vòng lặp chặt chẽ trên NFS từ nhiều khách hàng đồng thời và tính thành công và dường như vẫn còn một số điều kiện chủng tộc ở đó, dường như nếu hai khách hàng đưa ra một liên kết cứng hoạt động cùng một lúc, với cùng một đích, cả hai dường như thành công. (Có thể hành vi này có liên quan đến việc triển khai máy chủ NFS cụ thể, YMMV.) Trong mọi trường hợp, đó có thể là cùng một loại điều kiện chủng tộc, trong đó bạn có thể sẽ nhận được hai nút riêng biệt cho cùng một tệp trong trường hợp nặng đồng thời giữa các nhà văn để kích hoạt các điều kiện chủng tộc. Nếu các nhà văn của bạn nhất quán (cả sao chép A đến B) và độc giả của bạn chỉ tiêu thụ nội dung, điều đó có thể là đủ.

Cuối cùng, bạn đã đề cập đến khóa. Thật không may, khóa rất thiếu, ít nhất là trong NFSv3 (không chắc chắn về NFSv4, nhưng tôi cá là nó cũng không tốt.) Nếu bạn đang xem xét khóa, bạn nên xem xét các giao thức khác nhau để khóa phân tán, có thể nằm ngoài băng tần với bản sao tệp thực tế, nhưng cả hai đều gây rối, phức tạp và dễ xảy ra các vấn đề như bế tắc, vì vậy tôi nên nói rằng tốt hơn nên tránh.


Để có thêm thông tin về chủ đề nguyên tử trên NFS, bạn có thể muốn đọc trên định dạng hộp thư Maildir , được tạo để tránh khóa và hoạt động đáng tin cậy ngay cả trên NFS. Nó làm như vậy bằng cách giữ tên tệp duy nhất ở mọi nơi (vì vậy bạn thậm chí không nhận được B cuối cùng ở cuối.)

Có lẽ hơi thú vị hơn với trường hợp cụ thể của bạn, định dạng Maildir ++ mở rộng Maildir để thêm hỗ trợ cho hạn ngạch hộp thư và làm như vậy bằng cách cập nhật nguyên bản một tệp có tên cố định bên trong hộp thư (vì vậy có thể gần với B. của bạn hơn) để chắp thêm, điều này không thực sự an toàn trên NFS, nhưng có một cách tiếp cận tính toán lại sử dụng một quy trình tương tự như điều này và nó có giá trị như một sự thay thế nguyên tử.

Hy vọng tất cả những gợi ý này sẽ hữu ích!


2

Bạn có thể viết một chương trình cho việc này.

Sử dụng open(O_CREAT|O_RDWD)để mở tệp đích, đọc tất cả các byte và siêu dữ liệu để kiểm tra xem tệp đích có phải là một tệp hoàn chỉnh hay không, nếu không, có hai khả năng,

  1. Viết không đầy đủ

  2. Quá trình khác đang chạy cùng một chương trình.

Cố gắng khóa một khóa mô tả tệp mở trên tệp đích.

Thất bại có nghĩa là có một quy trình đồng thời, quy trình hiện tại nên tồn tại.

Thành công có nghĩa là lần ghi cuối cùng bị lỗi, bạn nên bắt đầu lại hoặc cố gắng sửa nó bằng cách ghi vào tệp.

Cũng lưu ý rằng bạn sẽ tốt hơn fsync()sau khi ghi vào tệp đích trước khi bạn đóng tệp và giải phóng khóa, hoặc quá trình khác có thể đọc dữ liệu chưa có trên đĩa.

https://www.gnu.org/software/libc/manual/html_node/Open-File-Descrip-Locks.html

Điều này rất quan trọng để giúp bạn phân biệt giữa chương trình đang chạy đồng thời và hoạt động bị lỗi cuối cùng.


Cảm ơn thông tin, tôi quan tâm để thực hiện điều này bản thân mình và sẽ cho nó đi. Tôi ngạc nhiên khi nó không tồn tại như một phần của một số coreutils / gói tương tự!
Evan Benn

Cách tiếp cận này không thể đáp ứng tệp B bị hỏng một phần hoặc bị hỏng tại chỗ theo yêu cầu sự cố . Tốt nhất là sử dụng cách tiếp cận tiêu chuẩn của việc sao chép tệp sang tên tạm thời, sau đó di chuyển nó vào vị trí: di chuyển có thể là nguyên tử, sao chép không thể.
rebierpost

@reinierpost Nếu gặp sự cố, nhưng dữ liệu không được sao chép hoàn toàn, dữ liệu được sao chép một phần sẽ không còn vấn đề gì. Nhưng cách tiếp cận của tôi sẽ phát hiện điều này và khắc phục nó. Di chuyển tệp không thể là nguyên tử, mọi dữ liệu được ghi vào ổ đĩa vật lý chéo sẽ không phải là nguyên tử, nhưng phần mềm (ví dụ: Trình điều khiển hệ thống tệp OS, phương pháp này) có thể sửa nó (nếu rw) hoặc báo cáo trạng thái nhất quán (nếu ro) , như đã đề cập trong phần bình luận của câu hỏi. Ngoài ra câu hỏi là về sao chép, không di chuyển.
炸鱼 薯条

Tôi cũng đã thấy O_TMPFILE, có lẽ sẽ giúp ích. (và nếu không có sẵn trên FS, sẽ gây ra lỗi)
Evan Benn

@Evan bạn đã đọc tài liệu hay bạn đã bao giờ nghĩ tại sao O_TMPFILE lại dựa vào hỗ trợ hệ thống tập tin chưa?
炸鱼 薯条

0

Bạn sẽ nhận được kết quả chính xác bằng cách làm cpcùng mv. Điều này sẽ thay thế "B" bằng một bản sao mới của "A" hoặc để lại "B" như trước đây.

cp A B.tmp && mv B.tmp B

cập nhật cho chỗ ở hiện có B:

cp A B.tmp && if [ ! -e B ]; then mv B.tmp B; else rm B.tmp; fi

Đây không phải là nguyên tử 100%, nhưng nó đã gần. Có một điều kiện cuộc đua trong đó hai trong số những thứ này đang chạy, cả hai đều tham gia ifthử nghiệm cùng một lúc, cả hai đều thấy điều Bđó không tồn tại, sau đó cả hai thực hiện mv.


mv B.tmp B sẽ ghi đè lên B. cp A B.tmp trước đó sẽ ghi đè lên B.tmp tồn tại trước, cả hai đều thất bại.
Evan Benn

mv B.tmp Bsẽ không chạy trừ khi cp A B.tmplần đầu tiên chạy và trả về mã kết quả thành công. Làm thế nào là một thất bại? Ngoài ra, tôi đồng ý rằng cp A B.tmpsẽ ghi đè lên một thứ hiện có B.tmp, đó là những gì bạn muốn làm. Các &&đảm bảo rằng lệnh thứ 2 sẽ chạy khi và chỉ khi lệnh đầu tiên hoàn thành bình thường.
kaan

Trong câu hỏi thành công được định nghĩa là không ghi đè lên tệp có sẵn B. Sử dụng B.tmp là một cơ chế, nhưng cũng không được ghi đè lên bất kỳ tệp nào có sẵn.
Evan Benn

Tôi cập nhật câu trả lời của tôi. Cuối cùng, nếu bạn cần nguyên tử hoàn toàn 100% khi các tệp có thể tồn tại hoặc không tồn tại và nhiều luồng, bạn cần một khóa độc quyền ở đâu đó (tạo một tệp đặc biệt hoặc sử dụng cơ sở dữ liệu hoặc ...) mà mọi người theo dõi như một phần của sao chép / di chuyển quá trình.
kaan

Bản cập nhật này vẫn ghi đè lên B.tmp và có điều kiện chạy đua giữa thử nghiệm và mv. Vâng, vấn đề là làm mọi thứ một cách chính xác, gần như không đủ tốt để hy vọng. Các câu trả lời khác cho thấy tại sao khóa và cơ sở dữ liệu là không cần thiết.
Evan Benn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.