โปรแกรมที่สามารถดำเนินการต่อการถ่ายโอนไฟล์ล้มเหลวรู้ได้อย่างไรว่าจะเริ่มต่อท้ายข้อมูลได้อย่างไร


23

บางโปรแกรมคัดลอกไฟล์เช่นrsyncและcurlมีความสามารถในการดำเนินการถ่ายโอน / สำเนาล้มเหลว

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

เมื่อโปรแกรมเหล่านี้ทำงานต่อพวกเขาดูเหมือนจะคำนวณขนาดของไฟล์ / ข้อมูลที่ถ่ายโอนได้สำเร็จและเพิ่งเริ่มอ่านไบต์ถัดไปจากแหล่งที่มาและผนวกเข้ากับส่วนของไฟล์

เช่นขนาดของไฟล์แฟรกเมนต์ที่ "ทำให้เป็น" ไปยังปลายทางคือ 1378 ไบต์ดังนั้นพวกเขาจึงเริ่มอ่านจากไบต์ 1379 บนต้นฉบับและเพิ่มลงในแฟรกเมนต์

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

เมื่อเขียนไฟล์ปลายทางจะมีการบัฟเฟอร์หรือ "การทำธุรกรรม" คล้ายกับฐานข้อมูล SQL ที่เกิดขึ้นไม่ว่าจะเป็นที่ระดับเคอร์เนลของโปรแกรมหรือระบบไฟล์
หรือโปรแกรมสันนิษฐานว่าไบต์ล่าสุดอาจไม่สมบูรณ์ดังนั้นพวกเขาจึงลบมันโดยสมมติว่ามันเลวร้ายแล้วทำการคัดกรองไบต์ใหม่และเริ่มต้นการต่อท้ายจากที่นั่น?

เมื่อรู้ว่าไม่ใช่ข้อมูลทั้งหมดที่แสดงเป็นไบต์การคาดเดาเหล่านี้ดูเหมือนจะไม่ถูกต้อง

เมื่อโปรแกรมเหล่านี้ "กลับมา" พวกเขารู้ได้อย่างไรว่าพวกเขาเริ่มต้นในที่ที่ถูกต้อง?


21
"ไม่ใช่ไฟล์ทั้งหมดที่แบ่งข้อมูลเป็นกลุ่มขนาดไบต์ที่สะอาด" ใช่ไหม คุณจะเขียนอะไรที่น้อยกว่าไบต์ไปยังไฟล์ได้อย่างไร?
muru

17
ฉันรู้ว่าไม่มีการเรียกของระบบที่สามารถเขียนอะไรได้น้อยกว่าหนึ่งไบต์และสำหรับตัวดิสก์เองฉันคิดว่าวันนี้ไม่มีดิสก์ที่เขียนได้น้อยกว่า 512 ไบต์บล็อก (หรือ 4096 ไบต์บล็อก)
muru

8
ไม่ฉันกำลังบอกว่าค่าต่ำสุดคือไบต์ แอปพลิเคชันที่มีเหตุผลจะใช้ก้อน 4KB หรือ 8KB: head -c 20480 /dev/zero | strace -e write tee foo >/dev/nullจากนั้นระบบปฏิบัติการจะบัฟเฟอร์บัฟเฟอร์เหล่านั้นและส่งไปยังดิสก์ในกลุ่มก้อนที่ใหญ่กว่า
muru

9
@the_velour_fog: คุณจะเขียนยังfwrite()ไงดี?
psmears

9
สำหรับวัตถุประสงค์ในทางปฏิบัติทุกข้อมูลที่ถูกสร้างขึ้นจากไบต์และทุกอย่างดำเนินการกับพวกเขาเป็นหน่วยที่เล็กที่สุด ระบบบางระบบ (ส่วนใหญ่เกี่ยวข้องกับการบีบอัดเช่น gzip, h264) แกะบิตแต่ละอันออกมาจากไบต์ แต่ระบบปฏิบัติการและการทำงานของหน่วยความจำอยู่ที่ระดับไบต์
pjc50

คำตอบ:


40

เพื่อความชัดเจน - กลไกที่แท้จริงมีความซับซ้อนมากขึ้นเพื่อให้การรักษาความปลอดภัยที่ดียิ่งขึ้น - คุณสามารถจินตนาการการดำเนินการเขียนไปยังดิสก์เช่นนี้:

  • แอปพลิเคชันเขียนไบต์ (1)
  • เคอร์เนล (และ / หรือระบบไฟล์ IOSS) บัฟเฟอร์พวกเขา
  • เมื่อบัฟเฟอร์เต็มมันจะถูกฟลัชไปยังระบบไฟล์:
    • บล็อกถูกจัดสรร (2)
    • บล็อกถูกเขียน (3)
    • ข้อมูลไฟล์และบล็อกได้รับการอัพเดต (4)

หากกระบวนการถูกขัดจังหวะที่ (1) คุณจะไม่ได้รับอะไรเลยบนดิสก์ไฟล์จะไม่เสียหายและถูกตัดทอนที่บล็อกก่อนหน้า คุณส่ง 5,000 ไบต์เพียง 4096 อยู่บนดิสก์คุณรีสตาร์ทการถ่ายโอนที่ offset 4096

หากที่ (2) จะไม่มีอะไรเกิดขึ้นยกเว้นในความทรงจำ เช่นเดียวกับ (1) ถ้าใน (3) ข้อมูลเป็นลายลักษณ์อักษรแต่ไม่มีใครจำได้เกี่ยวกับเรื่องนี้ คุณส่ง 9000 ไบต์, 4096 เขียน, 4096 เขียนและสูญหายส่วนที่เหลือเพิ่งหายไป การดำเนินการต่อการถ่ายโอนที่ชดเชย 4096

หากที่ (4) ข้อมูลควรถูกส่งไปยังดิสก์แล้ว ไบต์ถัดไปในสตรีมอาจหายไป คุณส่ง 9000 ไบต์, 8192 รับการเขียนส่วนที่เหลือจะหายไปการถ่ายโอนประวัติที่ออฟเซ็ต 8192

นี้เป็นที่เรียบง่ายใช้เวลา ตัวอย่างเช่นการเขียนแบบ "ตรรกะ" ในขั้นตอนที่ 3-4 ไม่ใช่ "atomic" แต่ก่อให้เกิดลำดับอื่น (ลองหมายเลข # 5) โดยที่บล็อกแบ่งออกเป็นบล็อกย่อยที่เหมาะสมสำหรับอุปกรณ์ปลายทาง (เช่นฮาร์ดดิสก์ ) ถูกส่งไปยังโฮสต์คอนโทรลเลอร์ของอุปกรณ์ซึ่งมีกลไกการแคชและสุดท้ายถูกเก็บไว้บนแผ่นแม่เหล็ก ลำดับย่อยนี้ไม่ได้อยู่ภายใต้การควบคุมของระบบอย่างสมบูรณ์เสมอไปดังนั้นการมีข้อมูลที่ส่งไปยังฮาร์ดดิสก์จึงไม่รับประกันว่าจะถูกเขียนขึ้นจริงและสามารถอ่านได้

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

หากระบบได้รับการตั้งค่าใหม่ในระหว่างการทำธุรกรรมระบบสามารถกลับไปยังจุดตรวจสอบที่ยังคงอยู่ใกล้ที่สุด ข้อมูลที่เขียนยังสูญหายเช่นเดียวกับกรณี (1) แต่การเริ่มต้นใหม่จะดูแลสิ่งนั้น ไม่มีข้อมูลสูญหาย


1
คำอธิบายที่ดี ที่ทำให้รู้สึกมาก ดังนั้นหากกระบวนการทำให้ข้อมูลบล็อกไฟล์ (4) อัปเดตตลอดเวลาคุณรู้ว่าไบต์เหล่านั้นดี แล้วไบต์ใด ๆ ที่อยู่ในขั้นตอนก่อนหน้านี้ไม่ได้ทำให้ดิสก์หรือ - ถ้าพวกเขาทำ - พวกเขาจะเป็น "ไม่จำ" (ไม่มีการอ้างอิงถึงพวกเขา)
the_velour_fog

4
@the_velour_fog และเพื่อเสริมความสมบูรณ์ของย่อหน้าสุดท้าย - หากคุณใช้ระบบไฟล์ที่ไม่ได้ใช้การทำเจอร์นัลคุณสามารถได้รับข้อมูล "เสีย" อย่างแน่นอนทำให้เรซูเม่ล้มเหลวและสร้างไฟล์ที่อ่านไม่ออกโดยไม่มีข้อผิดพลาด สิ่งนี้เคยเกิดขึ้นตลอดเวลาในอดีตโดยเฉพาะอย่างยิ่งกับระบบไฟล์ที่ออกแบบมาสำหรับอุปกรณ์ที่มีความหน่วงสูง (เช่น floppies) ยังมีเทคนิคบางอย่างที่จะหลีกเลี่ยงปัญหานี้แม้ว่าระบบไฟล์จะไม่น่าเชื่อถือ แต่อย่างใด แต่ต้องการแอปพลิเคชั่นที่ชาญฉลาดเพื่อชดเชยและข้อสันนิษฐานบางอย่างที่อาจผิดในบางระบบ
Luaan

คำตอบนี้แสดงถึงประโยชน์ของการทำเจอร์นัลในระบบไฟล์ มันไม่สามารถทำงานได้อย่างน่าเชื่อถือเว้นแต่ทุกอย่างใช้ความหมายของการทำธุรกรรมรวมถึงแอปพลิเคชัน userspace (ผ่านfsync) และตัวควบคุมฮาร์ดไดรฟ์ (มักจะใช้งานไม่ได้ หากไม่มีfsyncการดำเนินการไฟล์จำนวนมากที่สั่งซื้อโดยสังหรณ์ใจและไม่รับประกันว่าจะเป็นเช่นนั้นโดย POSIX: ไฟล์ที่เปิดด้วยO_APPENDอาจทำงานแตกต่างจากที่ไม่มี ฯลฯ ในทางปฏิบัติกุญแจที่สำคัญที่สุดสำหรับความสอดคล้องของไฟล์คือระบบเคอร์เนล VFS และดิสก์แคช ทุกอย่างอื่นส่วนใหญ่เป็นขุย
user1643723

11

หมายเหตุ: ฉันไม่ได้ดูที่แหล่งที่มาrsyncหรือยูทิลิตี้การถ่ายโอนไฟล์อื่น ๆ

มันเป็นเรื่องเล็กน้อยที่จะเขียนโปรแกรม C ที่ข้ามจุดสิ้นสุดของไฟล์และรับตำแหน่งของตำแหน่งนั้นเป็นไบต์

การดำเนินการทั้งสองเสร็จสิ้นด้วยการเรียกใช้ครั้งเดียวไปยังฟังก์ชันไลบรารี C มาตรฐานlseek()( lseek(fd, 0, SEEK_END)ส่งคืนความยาวของไฟล์ที่เปิดสำหรับตัวอธิบายไฟล์fdวัดเป็นไบต์)

หลังจากที่เสร็จสิ้นสำหรับแฟ้มเป้าหมายโทรคล้ายกับอาจจะทำในแฟ้มแหล่งที่มาเพื่อข้ามไปยังตำแหน่งที่เหมาะสม:lseek() lseek(fd, pos, SEEK_SET)การถ่ายโอนอาจดำเนินต่อไปที่จุดนั้นโดยสมมติว่าส่วนก่อนหน้าของไฟล์ต้นฉบับได้รับการระบุว่าไม่มีการเปลี่ยนแปลง

ไฟล์อาจมีการแยกส่วนบนดิสก์ แต่ระบบไฟล์จะตรวจสอบให้แน่ใจว่าแอปพลิเคชันรับรู้ไฟล์เป็นลำดับของไบต์


เกี่ยวกับการอภิปรายในความคิดเห็นเกี่ยวกับบิตและไบต์: หน่วยที่เล็กที่สุดของข้อมูลที่อาจจะเขียนไปยังดิสก์เป็นไบต์ ไบต์เดียวต้องการอย่างน้อยหนึ่งบล็อกของข้อมูลที่จะจัดสรรบนดิสก์ ขนาดของบล็อกขึ้นอยู่กับประเภทของระบบไฟล์และอาจขึ้นอยู่กับพารามิเตอร์ที่ผู้ดูแลระบบใช้เมื่อเริ่มต้นระบบไฟล์ แต่โดยทั่วไปจะอยู่ระหว่าง 512 ไบต์และ 4 KiB การดำเนินการเขียนอาจถูกบัฟเฟอร์โดยเคอร์เนลไลบรารี C พื้นฐานหรือโดยแอ็พพลิเคชันเองและการเขียนลงดิสก์จริงอาจเกิดขึ้นได้หลายขนาดบล็อกที่เหมาะสมเป็นการปรับให้เหมาะสม

เป็นไปไม่ได้ที่จะเขียนบิตเดียวไปยังไฟล์และหากการดำเนินการเขียนล้มเหลวมันจะไม่ปล่อย "ครึ่งเขียนครึ่ง" ในไฟล์


ขอบคุณดังนั้นสิ่งที่ช่วยให้มั่นใจว่าการดำเนินการเขียนล้มเหลว - มันจะไม่ทิ้งครึ่งเขียนไบต์? เคอร์เซอร์การบัฟเฟอร์เคอร์เนลอธิบายหรือไม่ - เช่นถ้ากระบวนการถูกขัดจังหวะในช่วงกลางของการส่ง 8KB อันไปยังเคอร์เนลและถูกยกเลิกโดยไม่คาดคิด - 8KB อันที่จะไม่ไปถึงเคอร์เนล - แต่ก่อนหน้านี้ใด ๆ ที่มาถึงเคอร์เนลและระบบไฟล์อาจจะถือว่าดี?
the_velour_fog

6
@the_velour_fog การเรียงลำดับของการเลิกจ้างที่ไม่คาดคิดไม่สามารถเกิดขึ้นได้เนื่องจากกระบวนการนี้จะไม่สามารถหยุดชะงักในช่วงกลางของการเรียกใช้ระบบ I / O (นั่นเป็นเหตุผลว่าทำไมมันจึงไม่ใช่เรื่องผิดปกติที่จะเห็นกระบวนการที่ไม่มีทักษะ ดูเพิ่มเติมที่: unix.stackexchange.com/q/62697/70524
muru

2
อาจมีปัญหาหากระบบสูญเสียพลังงานในเวลาที่ผิด ซึ่งอาจส่งผลให้ขยะที่จุดเขียนสุดท้ายของไฟล์เป็นครั้งคราว มันเป็นปัญหาที่ยุ่งยากมากในการออกแบบฐานข้อมูล แต่ยังคงเป็นหน่วยที่เล็กที่สุดปกติที่เป็น "ถูกต้อง" หรือ "ไม่ถูกต้อง" เป็นบล็อกดิสก์
pjc50

1
@the_velour_fog มันไม่มากเท่าที่คุณจะไม่สามารถรับ " half write bytes " (หรือแม่นยำยิ่งขึ้นครึ่งบล็อกที่เขียนเป็นไบต์) เนื่องจากบล็อกที่เขียนครึ่งหนึ่งจะไม่ถูกบันทึกตามที่เขียนไว้ (ทั้งหมด ) - ดูขั้นตอน (3) และ (4) ของคำตอบของ LSerni
TripeHound

5

นี่เป็นคำถามสองข้อเนื่องจากโปรแกรมอย่าง curl และ rsync นั้นแตกต่างกันมาก

สำหรับไคลเอนต์ HTTP เช่น curl พวกเขาตรวจสอบขนาดของไฟล์ปัจจุบันแล้วส่งContent-Rangeส่วนหัวพร้อมคำขอของพวกเขา เซิร์ฟเวอร์จะทำการส่งต่อช่วงของไฟล์โดยใช้รหัสสถานะ206(เนื้อหาบางส่วน) แทนที่จะเป็น200(สำเร็จ) และการดาวน์โหลดจะกลับมาทำงานต่อหรือไม่สนใจส่วนหัวและเริ่มต้นจากจุดเริ่มต้นและไคลเอนต์ HTTP ไม่มีทางเลือกอื่น อีกครั้ง

เพิ่มเติมเซิร์ฟเวอร์อาจหรือไม่อาจส่งContent-Lengthส่วนหัว คุณอาจสังเกตเห็นว่าการดาวน์โหลดบางรายการไม่แสดงเปอร์เซ็นต์และขนาดไฟล์ เป็นการดาวน์โหลดที่เซิร์ฟเวอร์ไม่บอกความยาวของไคลเอ็นต์ดังนั้นไคลเอนต์จะทราบจำนวนที่ดาวน์โหลดเท่านั้น แต่จะไม่ตามจำนวนไบต์

การใช้Content-Rangeส่วนหัวที่มีตำแหน่งเริ่มต้นและหยุดจะถูกใช้โดยตัวจัดการดาวน์โหลดบางตัวเพื่อดาวน์โหลดไฟล์จากแหล่งต่าง ๆ ในคราวเดียวซึ่งจะเพิ่มความเร็วในการถ่ายโอนหากมิเรอร์แต่ละตัวนั้นช้ากว่าการเชื่อมต่อเครือข่ายของคุณ

rsync ในทางกลับกันเป็นโปรโตคอลขั้นสูงสำหรับการถ่ายโอนไฟล์ที่เพิ่มขึ้น มันสร้าง checksums ของส่วนต่าง ๆ ของไฟล์บนฝั่งเซิร์ฟเวอร์และไคลเอนต์เพื่อตรวจสอบว่าไบต์ใดเหมือนกัน จากนั้นจะส่งความแตกต่างเท่านั้น ซึ่งหมายความว่ามันไม่เพียง แต่สามารถทำการดาวน์โหลดต่อได้เท่านั้น แต่ยังสามารถดาวน์โหลดไบต์ที่ถูกเปลี่ยนได้หากคุณเปลี่ยนสองสามไบต์ในกลางไฟล์ที่มีขนาดใหญ่มากโดยไม่ต้องดาวน์โหลดไฟล์อีกครั้ง

โปรโตคอลอื่นที่ทำขึ้นเพื่อให้การถ่ายโอนต่อนั้นเป็นบิตบิตซึ่ง.torrentไฟล์นั้นมีรายการ checksums สำหรับบล็อกจากไฟล์ดังนั้นบล็อกสามารถดาวน์โหลดและตรวจสอบได้ตามลำดับโดยพลการและขนานกันจากแหล่งต่าง ๆ

โปรดทราบว่า rsync และ bittorent จะตรวจสอบข้อมูลบางส่วนในดิสก์ของคุณในขณะที่การดาวน์โหลด HTTP ต่อไปจะไม่เกิดขึ้น ดังนั้นหากคุณสงสัยว่าข้อมูลบางส่วนเสียหายคุณจำเป็นต้องตรวจสอบความถูกต้องเช่นใช้การตรวจสอบไฟล์สุดท้าย แต่การขัดจังหวะการดาวน์โหลดหรือการสูญเสียการเชื่อมต่อเครือข่ายมักจะไม่ทำให้ไฟล์บางส่วนเสียหายในขณะที่ไฟดับระหว่างการถ่ายโอนอาจทำได้


4

TL: DR: พวกเขาทำไม่ได้ยกเว้นว่าพวกเขาใช้โปรโตคอลที่อนุญาต

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

หากการดาวน์โหลดไฟล์ถูกหยุดชั่วคราวหรือหยุดการทำงานชั่วคราวไคลเอ็นต์สามารถเขียนไฟล์ไปยังดิสก์และมีแนวคิดที่ชัดเจนว่าจะกลับมาทำงานต่อที่ใด หากในทางกลับกันไคลเอนต์ขัดข้องหรือมีข้อผิดพลาดในการเขียนไฟล์ไคลเอนต์จะต้องสมมติว่าไฟล์เสียหายและเริ่มต้นใหม่ BitTorrentช่วยบรรเทาปัญหานี้ได้ด้วยการแบ่งไฟล์เป็น "ชิ้นส่วน" และติดตามว่าไฟล์ใดถูกดาวน์โหลดสำเร็จแล้ว ส่วนใหญ่ที่จะต้องทำซ้ำเป็นชิ้น ๆ Rsyncทำสิ่งที่คล้ายกัน

โปรแกรมรู้ได้อย่างไรว่าเนื้อหาเหมือนกัน? วิธีหนึ่งคือการตรวจสอบว่าตัวระบุบางตัวเหมือนกันระหว่างไคลเอ็นต์และเซิร์ฟเวอร์ ตัวอย่างนี้จะเป็นเวลาและขนาด แต่มีกลไกที่สามารถเฉพาะกับโปรโตคอล หากตัวระบุตรงกันลูกค้าสามารถสันนิษฐานได้ว่าการทำงานต่อจะทำงานได้

หากคุณต้องการการยืนยันที่ชัดเจนยิ่งขึ้น HTTP และเพื่อน ๆ ไม่ควรเป็นตัวเลือกแรกของคุณ คุณจะต้องการใช้โปรโตคอลที่มี checksum หรือ hash สำหรับไฟล์ทั้งหมดและ chan tranferred แต่ละอันเพื่อให้คุณสามารถเปรียบเทียบ checksum ของการดาวน์โหลดกับ checksum ของเซิร์ฟเวอร์: สิ่งที่ไม่ตรงกันจะถูกดาวน์โหลดอีกครั้ง อีกครั้ง BitTorrent เป็นตัวอย่างของโปรโตคอลประเภทนี้ rsync เลือกที่จะทำเช่นนี้ด้วย


สำหรับตัวอย่าง rsync มันจะตรงไปตรงมาเพราะมีเพียงหนึ่งโปรโตคอล rsync สำหรับการดาวน์โหลด http มีการร้องขอช่วงเป็นมาตรฐาน ฉันอยากรู้ว่า curl ทำอะไรกับการอัพโหลดประวัติจริงเพราะความหมายมาตรฐานของการอัพโหลดคือหลายส่วน / แบบฟอร์มข้อมูล (สำหรับ wget และ curl) แต่ฉันไม่เชื่อว่าความหมายของการอัปโหลดเรซูเม่นั้นได้รับการยอมรับในระดับสากล YouTube และ Nginx อาจทำสิ่งนี้ต่างออกไป
Rob

1

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.