โฟลเดอร์ชั่วคราวที่ทำลายโดยอัตโนมัติหลังจากกระบวนการออก


10

เราสามารถใช้โฟลเดอร์ชั่วคราวเช่นไฟล์ชั่วคราว

TMP=$(mktemp ... )
exec 3<>$TMP
rm $TMP

cat <&3

ซึ่งจะถูกทำลายโดยอัตโนมัติหลังจากที่เปลือกนี้จบการทำงาน


คำตอบ:


12

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

มันเท่าที่ฉันรู้ว่าเป็นไปไม่ได้ที่จะเปิดไดเรกทอรีในลักษณะเดียวกันอย่างน้อยก็ไม่ได้ในลักษณะที่จะทำให้ไดเรกทอรีใช้งานได้

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

tmpdir=$(mktemp -d)
tmpfile=$(mktemp)

trap 'rm -f "$tmpfile"; rm -rf "$tmpdir"' EXIT

# The rest of the script goes here.

หรือคุณอาจเรียกฟังก์ชันล้างข้อมูล

cleanup () {
    rm -f "$tmpfile"
    rm -rf "$tmpdir"
}

tmpdir=$(mktemp -d)
tmpfile=$(mktemp)

trap cleanup EXIT

# The rest of the script goes here.

EXITกับดักจะไม่ได้รับการดำเนินการเมื่อได้รับKILLสัญญาณ (ซึ่งไม่สามารถถูกขังอยู่) ซึ่งหมายความว่าจะไม่มีการทำความสะอาดดำเนินการแล้ว อย่างไรก็ตามมันจะดำเนินการเมื่อสิ้นสุดเนื่องจากสัญญาณINTหรือTERM(หากใช้งานกับbashหรือkshในเชลล์อื่น ๆ คุณอาจต้องการเพิ่มสัญญาณเหล่านี้หลังจากEXITในtrapบรรทัดคำสั่ง) หรือเมื่อออกจากปกติเนื่องจากมาถึงตอนท้ายของสคริปต์หรือดำเนินการexitโทร.


5
ไม่ใช่แค่เชลล์ที่ไม่สามารถใช้ไดเรกทอรีชั่วคราวที่ไม่ได้เชื่อมโยงแล้ว - ไม่สามารถโปรแกรม C ปัญหาคือไดเรกทอรีที่ไม่ได้เชื่อมโยงไม่สามารถมีไฟล์ได้ คุณสามารถมีไดเรกทอรีว่างเปล่าที่ไม่ได้เชื่อมโยงเป็นไดเรกทอรีทำงานของคุณ แต่ความพยายามใด ๆ ในการสร้างไฟล์จะทำให้เกิดข้อผิดพลาด
Derobert

1
@derobert และเช่นไดเรกทอรีที่ไม่ได้เชื่อมโยงไม่ได้มี.และ..รายการ (ทดสอบบนลินุกซ์ผมไม่ทราบว่าที่สอดคล้องกันข้ามแพลตฟอร์ม.)
kasperd


1
โปรดทราบว่ากับดัก EXIT จะไม่ทำงานหากสคริปต์เรียกexec another-commandอย่างชัดเจน
Stéphane Chazelas


6

เขียนเชลล์ฟังก์ชั่นที่จะถูกดำเนินการเมื่อสคริปต์ของคุณถ้าเสร็จแล้ว ในตัวอย่างด้านล่างฉันเรียกมันว่า 'cleanup' และตั้งกับดักที่จะดำเนินการในระดับทางออกเช่น: 0 1 2 3 6

trap cleanup 0 1 2 3 6

cleanup()
{
  [ -d $TMP ] && rm -rf $TMP
}

ดูโพสต์นี้สำหรับข้อมูลเพิ่มเติม


สิ่งเหล่านี้ไม่ใช่ "ระดับทางออก" แต่เป็นสัญญาณและคำตอบสำหรับคำถามที่คุณกำลังเชื่อมโยงเพื่ออธิบายเพียงแค่นั้น กับดักจะทำงานcleanupก่อนทางออกที่สะอาด (0) และเมื่อได้รับ SIGHUP (1), SIGINT (2), SIGQUIT (3) และ SIGABRT (6) มันจะไม่ทำงานcleanupเมื่อสคริปต์ออกเนื่องจาก SIGTERM, SIGSEGV, SIGKILL, SIGPIPE และอื่น ๆ นี่เป็นข้อบกพร่องที่เห็นได้ชัด
mosvy

6

คุณสามารถ chdir ลงไปแล้วลบออกโดยที่คุณไม่ต้องใช้เส้นทางภายใน:

#! /bin/sh
dir=`mktemp -d`
cd "$dir"
exec 4>file 3<file
rm -fr "$dir"

echo yes >&4    # OK
cat <&3         # OK

cat file        # FAIL
echo yes > file # FAIL

ฉันไม่ได้ตรวจสอบ แต่อาจเป็นปัญหาเดียวกันที่สุดเมื่อใช้ openat (2) ใน C กับไดเรกทอรีที่ไม่มีอยู่ในระบบไฟล์อีกต่อไป

หากคุณรูทและบนลีนุกซ์คุณสามารถเล่นกับเนมสเปซที่แยกต่างหากและmount -t tmpfs tmpfs /dirข้างใน

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

ปรับปรุง:

นี่คือยูทิลิตี้ขนาดเล็กที่ใช้แนวทาง namespace มันควรจะรวบรวมด้วย

cc -Wall -Os -s chtmp.c -o chtmp

และCAP_SYS_ADMINความสามารถของไฟล์ (ตามรูท) ด้วย

setcap CAP_SYS_ADMIN+ep chtmp

เมื่อเรียกใช้ (ตามปกติ) ผู้ใช้เป็น

./chtmp command args ...

มันจะยกเลิกการแชร์ namespace ของระบบไฟล์ติดตั้งระบบไฟล์ tmpfs /proc/sysvipc, chdir ลงไปและเรียกใช้commandด้วยอาร์กิวเมนต์ที่กำหนด commandจะไม่สืบทอดCAP_SYS_ADMINความสามารถ

ระบบไฟล์นั้นจะไม่สามารถเข้าถึงได้จากกระบวนการอื่นที่ไม่ได้เริ่มต้นcommandและมันจะหายไปอย่างน่าอัศจรรย์ (ด้วยไฟล์ทั้งหมดที่สร้างขึ้นภายใน) เมื่อcommandและลูก ๆ ของมันตายไม่ว่าจะเกิดอะไรขึ้น โปรดสังเกตว่านี่เป็นเพียงการยกเลิกการแชร์เนมสเปซการเมาท์ - ไม่มีสิ่งกีดขวางที่ยากระหว่างcommandและกระบวนการอื่น ๆ ที่ดำเนินการโดยผู้ใช้เดียวกัน พวกเขายังคงสามารถแอบภายใน namespace ของทั้งผ่านptrace(2), /proc/PID/cwdหรือโดยวิธีการอื่น ๆ

การไฮแจ็กของ "ไร้ประโยชน์" /proc/sysvipcนั้นเป็นเรื่องไร้สาระ แต่ทางเลือกอื่นก็คือการทำสแปม/tmpด้วยไดเรกทอรีว่างที่จะต้องถูกลบออกหรือทำให้โปรแกรมขนาดเล็กนี้ซับซ้อนมากด้วยส้อมและรอ หรือdirสามารถเปลี่ยนเป็นเช่น /mnt/chtmpและสร้างโดยรูทเมื่อการติดตั้ง; อย่าทำให้ผู้ใช้สามารถกำหนดค่าได้และอย่าตั้งค่าเป็นเส้นทางที่ผู้ใช้เป็นเจ้าของเพราะอาจทำให้คุณต้องเชื่อมโยงกับ symps traps และสิ่งของที่มีขนดกอื่น ๆ ซึ่งไม่คุ้มค่ากับการใช้เวลา

chtmp.c

#define _GNU_SOURCE
#include <err.h>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mount.h>
int main(int argc, char **argv){
        char *dir = "/proc/sysvipc";    /* LOL */
        if(argc < 2 || !argv[1]) errx(1, "usage: %s prog args ...", *argv);
        argv++;
        if(unshare(CLONE_NEWNS)) err(1, "unshare(CLONE_NEWNS)");
        /* "modern" systemd remounts all mount points MS_SHARED
           see the NOTES in mount_namespaces(7); YUCK */
        if(mount("none", "/", 0, MS_REC|MS_PRIVATE, 0))
                err(1, "mount(/, MS_REC|MS_PRIVATE)");
        if(mount("tmpfs", dir, "tmpfs", 0, 0)) err(1, "mount(tmpfs, %s)", dir);
        if(chdir(dir)) err(1, "chdir %s", dir);
        execvp(*argv, argv);
        err(1, "execvp %s", *argv);
}

1
แม้ว่าคุณจะไม่รูทคุณสามารถทำได้ด้วยเนมสเปซโดยการสร้างเนมสเปซผู้ใช้ใหม่และทำการติดตั้ง tmpfs ภายใน การลักลอบเข้าถึง dir ใหม่ออกสู่โลกภายนอกนั้นค่อนข้างยุ่งยาก แต่น่าจะเป็นไปได้
. GitHub หยุดช่วยน้ำแข็ง

ยังคงต้องการ CAP_SYS_ADMIN ฉันมีความคิดเกี่ยวกับยูทิลิตีที่เปิดใช้งาน setcap ขนาดเล็กซึ่งจะทำเช่นนั้นฉันจะอัปเดตคำตอบด้วย
Qubert

1
ยกเว้นว่าเคอร์เนลถูกล็อคลงเพื่อไม่อนุญาตการสร้างเนมสเปซผู้ใช้ไม่ใช่การดำเนินการพิเศษ การออกแบบพื้นฐานนั้นควรจะปลอดภัยที่จะอนุญาตให้ผู้ใช้ทั่วไปทำโดยไม่มีความสามารถพิเศษใด ๆ อย่างไรก็ตามมีพื้นผิวการโจมตี / ความเสี่ยงที่เพียงพอที่ distros จำนวนมากปิดใช้งานฉันคิดว่า
.. GitHub หยุดช่วยน้ำแข็ง

ฉันพยายามที่สถานี ใน dir ชั่วคราวบางrm $PWDงานเชลล์ยังคงอยู่ใน dir นั้น แต่ไม่สามารถใส่ไฟล์ใหม่ลงใน "โฟลเดอร์" นี้ สิ่งเดียวที่คุณทำได้คืออ่าน / เขียนด้วยไฟล์ & 3, & 4 ดังนั้นนี่คือ "ไฟล์ชั่วคราว" ไม่ใช่ "โฟลเดอร์ชั่วคราว"
Bob Johnson

@BobJohnson ที่ไม่แตกต่างจากสิ่งที่ผมแล้วคำพูดในคำตอบของฉัน ;-)
qubert

0

คุณต้องการเชลล์ที่เฉพาะเจาะจงหรือไม่?

หาก zsh เป็นตัวเลือกโปรดอ่านzshexpn(1):

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

[ ... ]

ปัญหาอื่นเกิดขึ้นเมื่อใดก็ตามที่งานที่มีการทดแทนที่ต้องใช้ไฟล์ชั่วคราวถูกปิดใช้งานโดยเชลล์รวมถึงกรณีที่&!หรือ&|ปรากฏในตอนท้ายของคำสั่งที่มีการทดแทน ในกรณีนั้นไฟล์ชั่วคราวจะไม่ถูกล้างเนื่องจากเชลล์ไม่มีหน่วยความจำของงานอีกต่อไป วิธีแก้ปัญหาคือการใช้ subshell ตัวอย่างเช่น

(mycmd =(myoutput)) &!

เนื่องจากเชลล์ย่อยที่ถูกแยกจะรอให้คำสั่งเสร็จสิ้นจากนั้นลบไฟล์ชั่วคราว

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

() {
   print File $1:
   cat $1
} =(print This be the verse)

เอาท์พุทบางอย่างที่คล้ายกับต่อไปนี้

File /tmp/zsh6nU0kS:
This be the verse

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

# ~/.config/ranger/rifle.conf
...
!ext exe, mime octet-stream$, has gpg, flag t = () { rifle -f F "$1" } =(gpg -dq "$1")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.