Theppitak's blog

My personal blog.

10 ตุลาคม 2559

Crypto Disk Shutdown Problem Workaround (2)

จาก blog ที่แล้ว ที่ได้เขียนถึงการแก้ขัดปัญหา shutdown เครื่อง Debian ที่ใช้ sysvinit ไม่ลง อันเนื่องมาจากการค้างที่ขั้นตอนการปิด crypto disk ด้วยการไปแก้ไฟล์ /lib/cryptsetup/cryptdisks.functions นั้น หลังจากนั้นก็ได้ครุ่นคิดหาวิธีที่เหมาะสมกว่านั้น จนถึงจุดที่คิดว่าน่าจะลองเสนอใน Debian ได้

ปัญหาของการแก้แบบเดิมก็คือ:

  1. ไม่ idempotent เพราะใน do_stop() ไป stop ดีมอน แต่ใน do_start() ไม่ได้สั่ง start ดีมอนใหม่ เพราะสมมุติว่ามันถูก start มาแล้ว และทำแค่หา PID ของดีมอนมาใส่ใน omit file เท่านั้น ซึ่งจะทำให้เกิดปัญหาดีมอนตายได้หากผู้ใช้สั่ง stop, start หรือ restart cryptdisks ขณะที่เครื่องทำงานอยู่ ไม่ใช่ผ่านการ shutdown หรือ reboot ตามปกติ
  2. อาจกระทบผู้ใช้ init ระบบอื่น เพราะฟังก์ชันนี้อาจถูกเรียกใช้จากระบบ init อื่นก็ได้ ทั้งที่ระบบเหล่านั้นอาจไม่ได้มีปัญหานี้ และอาจเกิดผลกระทบไม่พึงประสงค์ได้
  3. ไม่สวยและไม่ปลอดภัย การอ่านค่า PID ด้วย ps, grep, awk ดูเยิ่นเย้อและไม่ปลอดภัย เพราะ awk อยู่ใน /usr/bin ซึ่งเสี่ยงต่อการเรียกในระหว่างการบูตที่อาจจะยังมีแค่ /bin หรือ /sbin ให้ใช้

ปัญหาการกระทบระบบ init อื่น ทำให้มองไปที่การแก้ /etc/init.d/* แทน ซึ่งในอีกแง่หนึ่ง ก็เป็นการแก้ในระดับบนซึ่งเป็นระดับเดียวกับที่ service อื่น ๆ ใช้จัดการกับ sendsigs อยู่แล้ว จึงสรุปว่าน่าจะเหมาะสมด้วยประการทั้งปวง

เบื้องต้นผมจึงไปแก้ที่ /etc/init.d/cryptdisks โดยทำหลังจาก do_start เสร็จแล้ว:

--- /etc/init.d/cryptdisks.orig 2016-10-08 17:14:02.087652932 +0700
+++ /etc/init.d/cryptdisks 2016-10-10 09:42:15.301303974 +0700
@@ -31,9 +31,19 @@
  ;;
 esac
 
+UDEVD_DAEMON="/lib/systemd/systemd-udevd"
+
 case "$1" in
 start)
  do_start
+
+ # Omit udev daemon on halt to allow cryptsetup to do the close
+ UDEVD_PID=$(pidof $UDEVD_DAEMON)
+ if [ ! -z "$UDEVD_PID" ]; then
+  OMITDIR=/run/sendsigs.omit.d
+  mkdir -p $OMITDIR
+  echo $UDEVD_PID > $OMITDIR/systemd-udevd
+ fi
  ;;
 stop)
  do_stop

สังเกตว่าในรอบนี้ผมไม่ได้สั่ง stop ดีมอนหลัง do_stop อีกแล้ว เพราะทำให้เกิดปัญหาไม่ idempotent ดังที่กล่าวไปแล้ว อีกทั้งมันไม่จำเป็นเลย เพราะ service script ของ udev เองก็จะทำหน้าที่ stop ดีมอนนี้ให้อยู่แล้ว สิ่งที่จำเป็นมีแค่ชะลอไม่ให้ดีมอนถูกฆ่าจนถึงตอนนั้นก็พอ

และสังเกตว่าผมไม่ได้ใช้สคริปต์เยิ่นเย้อในการหา PID ของดีมอนอีกแล้ว ในเมื่อสามารถใช้ /bin/pidof ที่สั้นกระชับและปลอดภัยกว่า

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

ในรอบที่แล้วผมทำกับ udev ไม่สำเร็จ คาดว่าเป็นเพราะไปแทรกโค้ดที่ลำดับต้น ๆ ตั้งแต่เพิ่งเรียกดีมอนใหม่ ๆ ซึ่ง file system อาจจะยังไม่พร้อม อีกทั้งการเรียกใช้ awk จาก /usr/bin ก็ยังสุ่มเสี่ยงมากในระหว่างบูตอีกด้วย

รอบนี้ผมจึงลองใหม่ให้หายคาใจ โดยไปแทรกโค้ดในลำดับท้ายสุด หลังจาก Waiting for /dev to be fully populated... เสร็จแล้ว:

--- /etc/init.d/udev.orig 2016-10-10 09:40:47.937302585 +0700
+++ /etc/init.d/udev 2016-10-10 09:42:59.701304680 +0700
@@ -203,6 +203,14 @@
     else
  log_action_end_msg 0 'timeout'
     fi
+
+    # Omit systemd-udevd on halt to allow cryptsetup to do the close
+    UDEVD_PID=$(pidof $DAEMON)
+    if [ ! -z "$UDEVD_PID" ]; then
+        OMITDIR=/run/sendsigs.omit.d
+        mkdir -p $OMITDIR
+        echo $UDEVD_PID > $OMITDIR/udev
+    fi
     ;;
 
     stop)

ตรวจความเรียบร้อยแล้วก็ลองรีบูตเครื่องดู... ผ่าน!

ถึงจุดนี้ ก็เลยคิดว่าควรลองเสนอเข้าบั๊ก Debian #791944 ดู (ข้อความ #103) เพื่อให้ผู้ดูแลพิจารณาตามความเหมาะสม

ป้ายกำกับ: ,

07 ตุลาคม 2559

Crypto Disk Shutdown Problem Workaround

เมื่อเช้านี้รีบูตเครื่องหลายรอบมาก กว่าจะได้เริ่มงาน เหตุเพราะถูกบั๊ก Debian #839888 ของ cryptsetup กัด สุดท้ายเลยได้นั่งลงแก้ปัญหา crypto disk ที่ทำให้ shutdown เครื่องไม่ลงมาเป็นปีเสียที

ผมใช้ encrypted file system ในโน้ตบุ๊กเครื่องปัจจุบันมาตั้งแต่เริ่มแรก แต่มาเริ่มมีปัญหาในรอบ Stretch นี้น่าจะเกือบปีแล้ว คือในการ shutdown เครื่องมันจะมาค้างตรงขั้น Stopping remaining crypto disks... แล้วก็ไม่ไปไหน แม้จะลองทิ้งไว้เป็นชั่วโมงก็ตาม สุดท้ายต้องยอมกดปุ่ม power ค้างเพื่อตัดไฟให้เครื่องมันดับ แล้วก็ให้มันมา recover journal ตอนเปิดเครื่องใหม่เอา

ปล่อยให้เป็นอย่างนี้มาเป็นปี เพราะยังไม่มีเวลาไปนั่งแก้ปัญหา ตัว cryptsetup เองก็มีอัปเดตใน Debian มาหลายรุ่น แต่ละรุ่นก็ได้แต่ภาวนาว่าจะมีการแก้ปัญหานี้ แต่ก็ไม่มี และในเมื่อเครื่องมันยังเปิดใช้งานได้ทุกวัน ผมเลยปล่อยให้มันเป็นอย่างนี้มาเรื่อย ๆ (ซึ่งไม่ควร)

จนกระทั่งเมื่อเช้ามันบูตไม่ขึ้น! เพราะรุ่น 2:1.7.2-1 มีบั๊กในสคริปต์ที่พิมพ์ผิด ซึ่งเป็นรายงานที่ผมเพิ่งจะได้อ่านหลังจากที่แก้ปัญหาด้วยตัวเองจนบูตเครื่องขึ้นมาต่อเน็ตได้แล้ว และปรากฏว่าแพตช์ที่แก้เองไปก็เหมือนกันกับแพตช์ในรายงานบั๊กเป๊ะ

แต่ก็ไม่ได้ทำให้ปัญหาเครื่องค้างตอน shutdown หายไป แต่ไหน ๆ ก็มือเปรอะไปแล้ว เลยนั่งไล่ต่อ จนกระทั่งไปพบคำสนทนาที่สาวไปจนถึงบั๊ก Debian #791944 ซึ่งทำให้รู้ว่าปัญหานี้เกิดเฉพาะกับระบบที่ใช้ sysvinit เท่านั้น ไม่เกิดกับ systemd และในความเห็น #72 Guilhem Moulin ได้พบว่าในขั้นตอนการ shutdown นั้น ดีมอน systemd-udevd ได้ถูก kill ไปก่อนที่จะถึงขั้นปิด crypto disk ทำให้คำสั่ง cryptsetup luksClose "$dst" ค้าง!

พิษ systemd อีกแล้วครับพี่น้อง!

แต่อย่างไรก็ดี ในเมื่อได้เบาะแสอย่างนี้แล้ว ถ้าเราสามารถทำให้ดีมอน systemd-udevd อยู่รอดจากการฆ่าจนถึงขั้นปิด crypto disk ได้ ปัญหาของเราก็จะหมดไป

แน่นอนว่าวิธีที่ถูกหลักการนั้น ต้องไปแก้ที่ระบบ init และสถานะล่าสุดของบั๊กดังกล่าว ก็ได้ reassign ไปให้แพกเกจ initscripts แล้ว แต่เครื่องผมล่ะ? ขอแก้ปัญหาเฉพาะหน้าระหว่างรอละกัน

วิธีแก้ขัดของผมคือ ในขั้นตอน sendsigs ของการ shutdown เพื่อ Asking all remaining processes to terminate นั้น เราสามารถละเว้นบางโพรเซสจากการรับ SIGTERM ได้ โดยเพิ่มไฟล์ที่เก็บ PID ไว้ในไดเรกทอรี /run/sendsigs.omit.d/ ตัวอย่างของ service ที่ทำแบบนี้ก็เช่น rsyslog และ wpasupplicant ผมก็จัดการยืมมาใช้กับ cryptdisks เสีย โดยแก้ไฟล์ /lib/cryptsetup/cryptdisks.functions ในฟังก์ชัน do_start() ให้เพิ่ม PID ของ systemd-udevd ไว้ในรายชื่อโพรเซสละเว้น:

--- cryptdisks.functions.orig   2016-10-07 12:11:36.104693329 +0700
+++ cryptdisks.functions        2016-10-07 13:53:10.640533260 +0700
@@ -758,6 +758,11 @@ do_start () {
        done 3<&1
        umount_fs

+       # Omit udev daemon on halt to allow cryptsetup to do the close
+       OMITDIR=/run/sendsigs.omit.d
+       mkdir -p $OMITDIR
+       ps x | grep "systemd-udevd[ ]" | awk '{print $1}' > $OMITDIR/systemd-udevd
+
        log_action_end_msg 0
 }

เท่านี้ก็สามารถ shutdown เครื่องโดยไม่ค้างได้แล้ว (บางคนอาจเสนอให้ไปทำใน /etc/init.d/udev ไปเลย แต่ผมลองแล้ว มันทำให้ udev ไม่ start ตอนเปิดเครื่องเลยครับ บางที file system อาจยังไม่พร้อมในขั้นนั้นกระมัง?)

และเพื่อความแน่ใจ ผมจัดการ stop ดีมอนหลังจากที่ปิด crypto disk แล้วอีกชั้นหนึ่งด้วย ในฟังก์ชัน do_stop():

@@ -780,7 +785,11 @@ do_stop () {
                done 3<&1
        done

-       log_action_end_msg 0
+       # Kill udevd as we postponed it
+       OMITDIR=/run/sendsigs.omit.d
+       start-stop-daemon --stop -p $OMITDIR/systemd-udevd --user root --quiet --oknodo --retry 5
+
+       log_action_end_msg $?
 }

 # Convenience function to handle $VERBOSE

หมดไปครึ่งวัน แต่ตัดรำคาญเวลาเปิด-ปิดหรือรีบูตเครื่องไปได้เยอะเลยครับ และยังลดความเสี่ยงที่ file system จะเสียหายลงด้วย

ป้ายกำกับ: ,

hacker emblem