วิธีการคัดลอกไฟล์ทำธุรกรรม?


9

ฉันต้องการคัดลอกไฟล์จาก A ถึง B ซึ่งอาจอยู่ในระบบไฟล์ที่แตกต่างกัน

มีข้อกำหนดเพิ่มเติมบางประการ:

  1. การคัดลอกนั้นมีทั้งหมดหรือไม่มีไฟล์ไม่มีไฟล์ B บางส่วนหรือเสียหายที่เกิดความผิดพลาด
  2. อย่าเขียนทับไฟล์ที่มีอยู่ B;
  3. อย่าแข่งขันกับการดำเนินการพร้อมกันของคำสั่งเดียวกันอย่างมากหนึ่งสามารถประสบความสำเร็จ

ฉันคิดว่านี่เข้าใกล้:

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

แต่ 3. ถูกละเมิดโดย cp จะไม่ล้มเหลวหาก B.part มีอยู่ (แม้จะมีแฟล็ก -n) ต่อมา 1. อาจล้มเหลวหากกระบวนการอื่น 'ชนะ' cp และไฟล์ที่ลิงก์เข้าที่ไม่สมบูรณ์ B.part อาจเป็นไฟล์ที่ไม่เกี่ยวข้อง แต่ฉันยินดีที่จะล้มเหลวโดยไม่ลองใช้ชื่ออื่นที่ซ่อนอยู่ในกรณีนั้น

ฉันคิดว่า bash noclobber ช่วยได้ทำงานได้อย่างสมบูรณ์หรือไม่ มีวิธีรับโดยไม่มีข้อกำหนดรุ่น bash หรือไม่

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

การติดตามฉันรู้ว่าระบบไฟล์บางระบบจะล้มเหลวในตอนนี้ (NFS) มีวิธีตรวจสอบระบบไฟล์ดังกล่าวหรือไม่?

คำถามอื่นที่เกี่ยวข้อง แต่ไม่ใช่คำถามเดียวกัน:

การเคลื่อนที่ของอะตอมโดยประมาณในระบบไฟล์?

mv เป็นอะตอมบน fs ของฉันหรือไม่

มีวิธีในการย้ายไฟล์และไดเรกทอรีจาก tempfs ไปยัง ext4 partition บน eMMC หรือไม่

https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html


2
คุณกังวลเกี่ยวกับการดำเนินการพร้อมกันของคำสั่งเดียวกัน (เช่นสามารถล็อคภายในเครื่องมือของคุณเพียงพอ) หรือเกี่ยวกับการรบกวนจากภายนอกด้วยไฟล์เช่นกัน?
Michael Homer

3
"การทำธุรกรรม" อาจจะดีกว่า
muru

1
@MichaelHomer ภายในเครื่องมือดีพอฉันคิดว่าข้างนอกจะทำให้สิ่งต่าง ๆ ยากมาก! ถ้าเป็นไปได้ด้วยการล็อคไฟล์แม้ว่า ...
Evan Benn

1
@marcelm mvจะเขียนทับไฟล์ที่มีอยู่ B mv -nจะไม่แจ้งว่ามันล้มเหลว ln(1)( rename(2)) จะล้มเหลวหาก B มีอยู่แล้ว
Evan Benn

1
@EvanBenn จุดดี! ฉันควรอ่านความต้องการของคุณดีกว่า (ฉันมักจะต้องการการอัปเดตอะตอมมิกของเป้าหมายที่มีอยู่และฉันก็ตอบกลับโดยคำนึงถึงเรื่องนั้น)
31719

คำตอบ:


11

rsyncทำงานนี้ ไฟล์ชั่วคราวจะO_EXCLถูกสร้างขึ้นตามค่าเริ่มต้น (เฉพาะเมื่อคุณใช้--inplace) จากนั้นปิดrenamedทับไฟล์เป้าหมาย ใช้--ignore-existingเพื่อไม่เขียนทับ B หากมีอยู่

ในทางปฏิบัติฉันไม่เคยประสบปัญหาใด ๆ กับสิ่งนี้ใน ext4, zfs หรือแม้แต่ NFS mounts


rsync อาจทำสิ่งนี้ได้เป็นอย่างดี แต่หน้าคนที่ซับซ้อนมากทำให้ฉันตกใจ ตัวเลือกที่แสดงถึงตัวเลือกอื่น ๆ ไม่สามารถใช้ร่วมกันได้ ฯลฯ
Evan Benn

Rsync ไม่ช่วยตามข้อกำหนด # 3 เท่าที่ฉันสามารถบอกได้ ยังเป็นเครื่องมือที่ยอดเยี่ยมและคุณไม่ควรอายที่จะอ่านหน้าคน คุณสามารถลองgithub.com/tldr-pages/tldr/blob/master/pages/common/rsync.mdหรือcheat.sh/rsyncก็ได้ (tldr และ cheat เป็นสองโครงการที่แตกต่างกันซึ่งมีจุดมุ่งหมายเพื่อช่วยแก้ไขปัญหาที่คุณกล่าวไว้ ได้แก่ "man page is TL; DR" มีการสนับสนุนคำสั่งทั่วไปจำนวนมากและคุณจะเห็นประเพณีที่แสดงบ่อยที่สุด
sitaram

@EvanBenn rsync เป็นเครื่องมือที่ยอดเยี่ยมและคุ้มค่ากับการเรียนรู้! มันเป็น man page ที่ซับซ้อนเพราะมันมีประโยชน์หลายอย่าง อย่าถูกข่มขู่ :)
Josh

@sitaram, # 3 สามารถแก้ไขได้ด้วยไฟล์ pid สคริปต์เล็ก ๆ เช่นในคำตอบที่นี่
Robert Riedl

2
นี่คือคำตอบที่ดีที่สุด Rsync เป็นมาตรฐานอุตสาหกรรมสำหรับการถ่ายโอนไฟล์อะตอมและในการกำหนดค่าต่างๆสามารถตอบสนองทุกความต้องการของคุณ
wKavey

4

ไม่ต้องกังวลเป็นคุณลักษณะมาตรฐานnoclobber


ขอบคุณล่อลวงยอมรับคำตอบสั้น ๆ นี้ ความคิดเห็นใด ๆ เกี่ยวกับระบบไฟล์หลบซึ่งชอบ NFS?
Evan Benn

@EvanBenn ฉันหมายถึงการเพิ่มว่าฉันไม่แน่ใจว่า NFS จะไปยุ่งกับคุณที่นี่ในบางวิธี แต่ฉันลืม
ilkkachu

4

คุณถามเกี่ยวกับ NFS โค้ดชนิดนี้มีแนวโน้มที่จะแตกภายใต้ NFS เนื่องจากการตรวจสอบnoclobberเกี่ยวข้องกับการดำเนินการ NFS สองรายการแยกกัน (ตรวจสอบว่ามีไฟล์อยู่สร้างไฟล์ใหม่) และกระบวนการสองรายการจากไคลเอนต์ NFS แยกต่างหากสองแห่งอาจเข้าสู่สภาวะการแข่งขัน ทั้งการตรวจสอบที่B.partยังไม่มีอยู่จากนั้นทั้งคู่ก็ดำเนินการสร้างมันให้สำเร็จเพราะพวกเขาเขียนทับกัน)

ไม่มีจริง ๆ ที่จะทำการตรวจสอบทั่วไปว่าระบบไฟล์ที่คุณเขียนไปจะสนับสนุนบางอย่างเช่นnoclobberอะตอมหรือไม่ คุณสามารถตรวจสอบประเภทของระบบไฟล์ไม่ว่าจะเป็น NFS แต่นั่นจะเป็นวิธีแก้ปัญหาและไม่จำเป็นต้องเป็นการรับประกัน ระบบไฟล์เช่น SMB / CIFS (Samba) มีแนวโน้มที่จะประสบปัญหาเดียวกัน ระบบไฟล์เปิดเผยผ่าน FUSE อาจหรืออาจทำงานไม่ถูกต้อง แต่ส่วนใหญ่ขึ้นอยู่กับการใช้งาน


วิธีการอาจจะดีกว่าที่จะหลีกเลี่ยงการปะทะในส่วนB.partขั้นตอนโดยใช้ชื่อไฟล์ที่ไม่ซ้ำกัน (ผ่านความร่วมมือกับตัวแทนอื่น ๆ ) noclobberเพื่อให้คุณไม่จำเป็นต้องขึ้นอยู่กับ ตัวอย่างเช่นคุณอาจรวมชื่อไฟล์ชื่อโฮสต์ของคุณ PID ​​และเวลาประทับ (+ อาจเป็นตัวเลขสุ่ม) เนื่องจากควรมีกระบวนการเดียวที่ทำงานภายใต้ PID เฉพาะในเวลาใดก็ตาม รับประกันเอกลักษณ์

ดังนั้นหนึ่งใน:

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.

หรือ:

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"

ดังนั้นหากคุณมีสภาพการแข่งขันระหว่างสองเอเจนต์พวกเขาทั้งสองจะดำเนินการต่อไป แต่การดำเนินการครั้งสุดท้ายจะเป็นแบบอะตอมดังนั้น B จะมีอยู่พร้อมสำเนา A ทั้งหมดหรือ B ไม่มีอยู่

คุณสามารถลดขนาดของการแข่งขันได้โดยตรวจสอบอีกครั้งหลังจากการคัดลอกและก่อนmvหรือlnการดำเนินการ แต่ยังคงมีสภาพการแข่งขันขนาดเล็กอยู่ที่นั่น แต่ไม่ว่าสภาพการแข่งขันจะเป็นอย่างไรเนื้อหาของ B ควรสอดคล้องกันโดยสมมติว่าทั้งสองกระบวนการพยายามสร้างมันขึ้นมาจาก A (หรือคัดลอกจากไฟล์ที่ถูกต้องซึ่งเป็นต้นกำเนิด

โปรดทราบว่าในสถานการณ์แรกmvเมื่อมีการแข่งขันกระบวนการสุดท้ายคือกระบวนการที่ชนะเนื่องจากการเปลี่ยนชื่อ (2)จะแทนที่ไฟล์ที่มีอยู่แบบอะตอม:

หากมีเส้นทางใหม่แล้วจะมีการแทนที่ด้วยอะตอมดังนั้นจึงไม่มีจุดที่กระบวนการอื่นที่พยายามเข้าถึงnewpathจะพบว่าหายไป [ ... ]

หากมีnewpathอยู่ แต่การดำเนินการล้มเหลวด้วยเหตุผลบางอย่างrename()รับประกันให้ออกจากอินสแตนซ์ของnewpathไว้

ดังนั้นจึงเป็นไปได้มากที่กระบวนการที่ใช้ B ในเวลานั้นอาจเห็นรุ่นที่แตกต่างกัน (inodes ที่แตกต่างกัน) ในระหว่างกระบวนการนี้ หากนักเขียนทุกคนพยายามที่จะคัดลอกเนื้อหาเดียวกันและผู้อ่านกำลังใช้เนื้อหาของไฟล์เพียงอย่างเดียวนั่นอาจจะใช้ได้ถ้าพวกเขาได้รับ inode ที่แตกต่างกันสำหรับไฟล์ที่มีเนื้อหาเดียวกันพวกเขาจะมีความสุขเหมือนกัน

วิธีที่สองที่ใช้ฮาร์ดลิงค์นั้นดูดีกว่า แต่ฉันจำได้ว่าทำการทดลองกับฮาร์ดลิงก์ในลูปที่แน่นบน NFS จากไคลเอนต์ที่เกิดขึ้นพร้อมกันจำนวนมากและการนับความสำเร็จและดูเหมือนว่ายังมีสภาพการแข่งขันอยู่บ้าง ดำเนินการในเวลาเดียวกันโดยมีปลายทางเดียวกันดูเหมือนว่าทั้งสองจะประสบความสำเร็จ (เป็นไปได้ว่าพฤติกรรมนี้เกี่ยวข้องกับการใช้งานเซิร์ฟเวอร์ NFS โดยเฉพาะคือ YMMV) ในกรณีใด ๆ นั่นอาจเป็นสภาพการแข่งขันแบบเดียวกันซึ่งคุณอาจได้รับ inodes แยกกันสองไฟล์สำหรับไฟล์เดียวกันในกรณีที่มีของหนัก เกิดขึ้นพร้อมกันระหว่างผู้เขียนเพื่อเรียกสภาพการแข่งขันเหล่านี้ หากผู้เขียนของคุณมีความสอดคล้องกัน (ทั้งการคัดลอก A ถึง B) และผู้อ่านของคุณกำลังบริโภคเนื้อหาเพียงอย่างเดียวนั่นอาจเพียงพอ

ในที่สุดคุณพูดถึงการล็อค น่าเสียดายที่การล็อคขาดอย่างรุนแรงอย่างน้อยใน NFSv3 (ไม่แน่ใจเกี่ยวกับ NFSv4 แต่ฉันพนันได้ว่ามันไม่ดีเช่นกัน) หากคุณกำลังพิจารณาการล็อคคุณควรพิจารณาโปรโตคอลที่แตกต่างกันสำหรับการล็อคแบบกระจาย การคัดลอกไฟล์จริง แต่นั่นเป็นปัญหาที่ยุ่งยากซับซ้อนและมีแนวโน้มที่จะเกิดปัญหาเช่นการหยุดชะงักดังนั้นฉันจึงควรหลีกเลี่ยงการดีกว่า


สำหรับพื้นหลังเพิ่มเติมเกี่ยวกับหัวเรื่องของ atomicity บน NFS คุณอาจต้องการอ่านในรูปแบบกล่องจดหมาย Maildirซึ่งถูกสร้างขึ้นเพื่อหลีกเลี่ยงการล็อคและทำงานได้อย่างน่าเชื่อถือแม้ใน NFS ทำได้โดยการเก็บชื่อไฟล์ที่ไม่ซ้ำกันทุกที่ (ดังนั้นคุณจะไม่ได้ B สุดท้ายในตอนท้าย)

บางทีค่อนข้างน่าสนใจสำหรับกรณีของคุณรูปแบบ Maildir ++ จะขยาย Maildir เพื่อเพิ่มการสนับสนุนสำหรับโควต้ากล่องจดหมายและทำได้โดยการอัปเดตไฟล์ด้วยชื่อคงที่ในกล่องจดหมาย (โดยทั่วไปอาจมีความใกล้ชิดกับ B. ) ฉันคิดว่าพยายาม Maildir ++ เพื่อผนวกซึ่งไม่ปลอดภัยสำหรับ NFS แต่มีวิธีการคำนวณใหม่ซึ่งใช้กระบวนการที่คล้ายกับสิ่งนี้และใช้ได้ในฐานะการแทนที่อะตอมมิก

หวังว่าพอยน์เตอร์ทั้งหมดเหล่านี้จะเป็นประโยชน์!


2

คุณสามารถเขียนโปรแกรมสำหรับสิ่งนี้

ใช้open(O_CREAT|O_RDWD)เพื่อเปิดไฟล์เป้าหมายอ่านไบต์และข้อมูลเมตาทั้งหมดเพื่อตรวจสอบว่าไฟล์เป้าหมายนั้นเป็นไฟล์ที่สมบูรณ์ถ้าไม่มีความเป็นไปได้สองอย่างคือ

  1. การเขียนไม่สมบูรณ์

  2. กระบวนการอื่นกำลังเรียกใช้โปรแกรมเดียวกัน

ลองรับการล็อคคำอธิบายไฟล์ที่เปิดในไฟล์เป้าหมาย

ความล้มเหลวหมายถึงมีกระบวนการที่เกิดขึ้นพร้อมกันและกระบวนการปัจจุบันควรมีอยู่จริง

ความสำเร็จหมายถึงการเขียนครั้งล่าสุดล้มเหลวคุณควรเริ่มต้นใหม่หรือลองแก้ไขด้วยการเขียนลงในไฟล์

นอกจากนี้โปรดสังเกตว่าคุณจะดีขึ้นfsync()หลังจากที่เขียนไปยังไฟล์เป้าหมายก่อนที่คุณจะปิดไฟล์และปล่อยการล็อกมิฉะนั้นกระบวนการอื่นอาจอ่านข้อมูลที่ยังไม่ได้อ่านบนดิสก์

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

นี่เป็นสิ่งสำคัญที่จะช่วยให้คุณแยกแยะระหว่างโปรแกรมที่รันพร้อมกันและการทำงานที่ล้มเหลวครั้งสุดท้าย


ขอบคุณสำหรับข้อมูลฉันสนใจที่จะใช้มันด้วยตัวเองและจะไปให้ได้ ฉันประหลาดใจที่มันไม่ได้มีอยู่เป็นส่วนหนึ่งของ coreutils / แพ็คเกจที่คล้ายกัน!
Evan Benn

วิธีการนี้ไม่สามารถทำตามไฟล์ B ที่ไม่ถูกต้องหรือเสียหายได้ เป็นการดีที่สุดที่จะใช้วิธีมาตรฐานในการคัดลอกไฟล์ไปยังชื่อชั่วคราวจากนั้นย้ายไปไว้ที่เดิม: การย้ายสามารถเป็นแบบอะตอมมิกซึ่งการคัดลอกไม่สามารถทำได้
reinierpost

@reinierpost หากเกิดความผิดพลาด แต่ข้อมูลยังไม่ถูกคัดลอกข้อมูลที่ถูกคัดลอกบางส่วนจะถูกทิ้งไว้ไม่ว่าจะเกิดอะไรขึ้น แต่วิธีการของฉันจะตรวจจับและแก้ไข การย้ายไฟล์ไม่สามารถเป็นอะตอมมิกข้อมูลใด ๆ ที่เขียนไปยังภาคส่วนข้ามดิสก์จะไม่เป็นอะตอมมิก แต่ซอฟต์แวร์ (เช่นไดรเวอร์ระบบไฟล์ระบบปฏิบัติการ OS วิธีนี้) สามารถแก้ไขได้ (ถ้า rw) หรือรายงานสถานะที่สอดคล้องกัน (ถ้า ro) ตามที่กล่าวไว้ในส่วนความเห็นของคำถาม คำถามก็เกี่ยวกับการคัดลอกไม่ใช่การเคลื่อนไหว
炸鱼薯条德里克

ฉันเห็น O_TMPFILE ด้วยซึ่งอาจช่วยได้ (และหากไม่มีใน FS ควรทำให้เกิดข้อผิดพลาด)
Evan Benn

@ ท่านได้อ่านเอกสารหรือเคยคิดบ้างไหมว่าทำไม O_TMPFILE ถึงต้องพึ่งพาระบบไฟล์ที่รองรับ?
炸鱼薯条德里克

0

คุณจะได้รับผลที่ถูกต้องด้วยการทำร่วมกับcp mvสิ่งนี้อาจแทนที่ "B" ด้วยสำเนาใหม่ของ "A" หรือปล่อย "B" เหมือนเดิม

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

อัปเดตเพื่อรองรับที่มีอยู่B:

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

นี่ไม่ใช่อะตอม 100% แต่เข้าใกล้แล้ว มีสภาพการแข่งขันที่สองของสิ่งเหล่านี้จะทำงานเป็นทั้งเข้าสู่ifการทดสอบในเวลาเดียวกันทั้งสองเห็นว่าไม่อยู่แล้วทั้งดำเนินการBmv


mv B.tmp B จะเขียนทับ B. cp ที่มีอยู่ก่อน A B.tmp จะเขียนทับ B.tmp ที่มีอยู่แล้วทั้งสองล้มเหลว
Evan Benn

mv B.tmp Bจะไม่ทำงานจนกว่าจะมีการเรียกใช้cp A B.tmpและส่งคืนรหัสผลลัพธ์สำเร็จ ความล้มเหลวเป็นอย่างไร นอกจากนี้ฉันเห็นด้วยที่cp A B.tmpจะเขียนทับที่มีอยู่B.tmpซึ่งเป็นสิ่งที่คุณต้องการจะทำ การ&&รับประกันว่าคำสั่งที่ 2 จะทำงานถ้าหากคำสั่งแรกเสร็จสมบูรณ์ตามปกติ
kaan

ในคำถามความสำเร็จถูกกำหนดเป็นไม่เขียนทับไฟล์ที่มีอยู่ล่วงหน้า B. การใช้ B.tmp เป็นกลไกหนึ่ง แต่ยังต้องไม่เขียนทับไฟล์ที่มีอยู่ก่อน
Evan Benn

ฉันปรับปรุงคำตอบของฉัน ท้ายที่สุดถ้าคุณต้องการอะตอมมิกเต็ม 100% เมื่อไฟล์อาจมีหรือไม่มีอยู่และหลายกระทู้คุณต้องล็อคแบบเอกสิทธิ์เฉพาะบุคคลที่ใดที่หนึ่ง (สร้างไฟล์พิเศษหรือใช้ฐานข้อมูลหรือ ... ) ที่ทุกคนติดตามเป็นส่วนหนึ่งของ กระบวนการคัดลอก / ย้าย
kaan

การอัปเดตนี้ยังคงเขียนทับ B.tmp และมีสภาวะการแย่งชิงระหว่างการทดสอบและ mv ใช่ประเด็นคือการทำสิ่งต่าง ๆ ไม่ถูกต้องอาจจะไม่ดีพอ คำตอบอื่น ๆ แสดงเหตุผลที่ไม่จำเป็นต้องใช้การล็อกและฐานข้อมูล
Evan Benn
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.