واصف ملف في لينكس مع أمثلة

ذات مرة ، في مقابلة ، سئلت ماذا ستفعل إذا وجدت خدمة معطلة بسبب حقيقة أن القرص قد نفد المساحة؟

بالطبع ، أجبته أنني سأرى ما كان يفعله هذا المكان ، وإذا أمكن ، سأقوم بتنظيف المكان.
ثم سأل القائم بإجراء المقابلة ، ماذا لو لم تكن هناك مساحة خالية على القسم ، لكنك أيضًا لا ترى الملفات التي من شأنها أن تشغل كل المساحة؟

لهذا ، قلت إنه يمكنك دائمًا إلقاء نظرة على واصفات الملفات المفتوحة ، على سبيل المثال ، باستخدام الأمر lsof وفهم التطبيق الذي شغل كل المساحة المتاحة ، ومن ثم يمكنك التصرف وفقًا للظروف ، اعتمادًا على ما إذا كانت البيانات مطلوبة .

قاطعني القائم بإجراء المقابلة عند الكلمة الأخيرة ، مضيفًا إلى سؤاله: "لنفترض أننا لسنا بحاجة إلى البيانات ، إنها مجرد سجل تصحيح ، ولكن التطبيق معطل لأنه لا يمكنه كتابة التصحيح"؟

أجبته "حسنًا" ، "يمكننا إيقاف تصحيح الأخطاء في تهيئة التطبيق وإعادة تشغيله".
اعترض القائم بإجراء المقابلة: "لا ، لا يمكننا إعادة تشغيل التطبيق ، لا يزال لدينا بيانات مهمة في الذاكرة ، والعملاء المهمون متصلون بالخدمة نفسها ، ولا يمكننا فرض إعادة الاتصال".

قلت "حسنًا" ، "إذا لم نتمكن من إعادة تشغيل التطبيق ولا نهتم بالبيانات ، فيمكننا مسح هذا الملف المفتوح عبر واصف الملف ، حتى لو لم نراه في ls الأمر على نظام الملفات. "

كان القائم بإجراء المقابلة راضيًا ، لكنني لم أكن كذلك.

ثم فكرت ، لماذا لا يتعمق الشخص الذي يختبر معرفتي؟ ولكن ماذا لو كانت البيانات مهمة بعد كل شيء؟ ماذا لو لم نتمكن من إعادة العملية ، وفي نفس الوقت تكتب هذه العملية إلى نظام الملفات على قسم لا يحتوي على مساحة خالية؟ ماذا لو لم نتمكن من فقدان ليس فقط البيانات المكتوبة بالفعل ، ولكن أيضًا البيانات التي تكتبها هذه العملية أو تحاول كتابتها؟

قارب

في بداية مسيرتي المهنية ، كنت أحاول إنشاء تطبيق صغير يحتاج إلى تخزين معلومات حول المستخدمين. ثم فكرت كيف يمكنني مطابقة المستخدم مع بياناته. على سبيل المثال ، لدي إيفانوف إيفان إيفانوفيتش ، ولديه بعض البيانات ، ولكن كيف أقوم بتكوين صداقات معهم؟ أستطيع أن أشير مباشرة إلى أن الكلب المسمى "Tuzik" ينتمي إلى نفس إيفان. ولكن ماذا لو غير اسمه وأصبح على سبيل المثال أوليا بدلاً من إيفان؟ بعد ذلك سيتضح أن أوليا إيفانوفنا إيفانوفا لن يكون لديها كلب بعد الآن ، وسيظل توزيك لدينا ينتمي إلى إيفان غير الموجود. ساعدت قاعدة البيانات في حل هذه المشكلة ، والتي أعطت لكل مستخدم معرفًا فريدًا (ID) ، وتم ربط Tuzik الخاص بي بهذا المعرف ، والذي كان في الواقع مجرد رقم تسلسلي. وهكذا ، كان مالك التوزيك يحمل رقم الهوية 2 ، وفي وقت ما كان إيفان يحمل هذا الرقم التعريفي ، ثم أصبحت أوليا تحت نفس المعرف. تم حل مشكلة الجنس البشري وتربية الحيوانات عمليا.

واصف الملف

مشكلة الملف والبرنامج الذي يعمل مع هذا الملف هي تقريباً نفس مشكلة كلبنا وإنساننا. لنفترض أنني فتحت ملفًا باسم ivan.txt وبدأت في كتابة كلمة tuzik فيه ، لكنني تمكنت من كتابة الحرف الأول فقط "t" في الملف ، وتمت إعادة تسمية هذا الملف من قِبل شخص ما ، على سبيل المثال ، إلى olya.txt. لكن الملف هو نفسه وما زلت أريد أن أكتب آصلي إليه. في كل مرة تفتح فيها ملفًا باستدعاء نظام جاكيت في أي لغة برمجة ، أحصل على معرف فريد يوجهني إلى ملف ، هذا المعرف هو واصف الملف. ولا يهم على الإطلاق ماذا ومن يفعل بعد ذلك مع هذا الملف ، يمكن حذفه ، ويمكن إعادة تسميته ، ويمكن تغيير مالكه أو سحب حقوق القراءة والكتابة ، وسيظل بإمكاني الوصول إليه ، لأنه في وقت فتح الملف كان لدي الحق في قراءته و / أو كتابته وتمكنت من البدء في العمل به ، مما يعني أنه يجب علي الاستمرار في ذلك.

في نظام Linux ، تفتح مكتبة libc 3 ملفات واصفات لكل تطبيق قيد التشغيل (عملية) ، بأرقام 0,1,2،XNUMX،XNUMX. يمكنك العثور على مزيد من المعلومات على الروابط رجل stdio и رجل stdout

  • يسمى واصف الملف 0 STDIN ويرتبط بإدخال التطبيق.
  • يسمى واصف الملف 1 STDOUT ويستخدم بواسطة تطبيقات الإخراج مثل أوامر الطباعة.
  • واصف الملف 2 يسمى STDERR ويستخدم من قبل التطبيقات لإخراج رسائل الخطأ.

إذا فتحت في برنامجك أي ملف للقراءة أو الكتابة ، فعلى الأرجح ستحصل على أول هوية مجانية وسيكون رقم 3.

يمكنك رؤية قائمة واصفات الملفات لأي عملية إذا كنت تعرف PID الخاص بها.

على سبيل المثال ، دعنا نفتح وحدة تحكم باستخدام bash ونرى PID لعمليتنا

[user@localhost ]$ echo $$
15771

في وحدة التحكم الثانية ، قم بتشغيل

[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

يمكنك تجاهل واصف الملف بأمان برقم 255 في إطار عمل هذه المقالة ، فقد تم فتحه لاحتياجاتك عن طريق bash نفسها ، وليس بواسطة المكتبة المرتبطة.

الآن جميع ملفات الوصف الثلاثة مرتبطة بالجهاز الطرفي الزائف / ديف / نقاط، ولكن لا يزال بإمكاننا التلاعب بها ، على سبيل المثال ، تشغيلها في وحدة التحكم الثانية

[user@localhost ]$ echo "hello world" > /proc/15771/fd/0

وسنرى في وحدة التحكم الأولى

[user@localhost ]$ hello world

إعادة التوجيه والأنابيب

يمكنك بسهولة تجاوز ملفات الوصف الثلاثة هذه في أي عملية ، بما في ذلك في bash ، على سبيل المثال ، من خلال أنبوب (أنبوب) يربط بين عمليتين ، راجع

[user@localhost ]$ cat /dev/zero | sleep 10000

يمكنك تشغيل هذا الأمر بنفسك باستخدام ستراس -f وأرى ما يجري في الداخل ، لكنني سأختصر الأمر.

تقوم عملية bash الأصل الخاصة بنا مع PID 15771 بتحليل أمرنا وتفهم بالضبط عدد الأوامر التي نريد تشغيلها ، وفي حالتنا يوجد اثنان منهم: القط والنوم. يعرف Bash أنه يحتاج إلى إنشاء عمليتين فرعيتين ، ودمجهما في أنبوب واحد. في المجموع ، ستحتاج bash إلى عمليتين فرعيتين وأنبوب واحد.

قبل إنشاء عمليات فرعية ، يقوم bash بتشغيل مكالمة نظام أنبوب ويتلقى واصفات ملفات جديدة على مخزن مؤقت للأنبوب ، لكن هذا المخزن المؤقت لا يربط بعد عمليتين فرعيتين بأي شكل من الأشكال.

بالنسبة للعملية الرئيسية ، يبدو أن الأنبوب موجود بالفعل ، لكن لا توجد عمليات فرعية حتى الآن:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21

ثم باستخدام مكالمة النظام استنساخ ينشئ bash عمليتين فرعيتين ، وستبدو عملياتنا الثلاث كما يلي:

PID    command
15771  bash
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
PID    command
9004  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21
PID    command
9005  bash
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 255 -> /dev/pts/21

لا تنس أن استنساخ استنساخ العملية مع جميع واصفات الملفات ، لذلك ستكون هي نفسها في العملية الأصلية وفي العمليات الفرعية. مهمة العملية الرئيسية مع PID 15771 هي مراقبة العمليات الفرعية ، لذلك تنتظر استجابة من الأطفال.

لذلك ، لا يحتاج إلى أنبوب ، ويغلق واصفات الملفات بالأرقام 3 و 4.

في أول عملية bash child باستخدام PID 9004 ، استدعاء النظام مزدوج 2، قم بتغيير واصف ملف STDOUT رقم 1 إلى واصف ملف يشير إلى أنبوب ، في حالتنا هو رقم 3. وبالتالي ، فإن كل ما يكتبه الطفل الأول مع PID 9004 إلى STDOUT سيقع تلقائيًا في المخزن المؤقت للأنبوب.

في العملية الفرعية الثانية مع PID 9005 ، bash dup2s الملف إلى رقم واصف STDIN رقم 0. الآن كل شيء ستقرأه bash الثانية مع PID 9005 سيقرأ من الأنبوب.

بعد ذلك ، يتم أيضًا إغلاق واصفات الملفات ذات الرقمين 3 و 4 في العمليات الفرعية ، نظرًا لأنها لم تعد مستخدمة.

لقد تجاهلت عمدًا واصف الملف 255 ، يتم استخدامه داخليًا بواسطة bash نفسه وسيتم إغلاقه أيضًا في العمليات الفرعية.

بعد ذلك ، في أول عملية فرعية باستخدام PID 9004 ، تبدأ bash باستدعاء النظام EXEC الملف القابل للتنفيذ الذي حددناه في سطر الأوامر ، في حالتنا هو / usr / bin / cat.

في العملية الفرعية الثانية باستخدام PID 9005 ، يقوم bash بتشغيل الملف التنفيذي الثاني الذي حددناه ، في حالتنا / usr / bin / sleep.

لا يغلق استدعاء نظام exec واصفات الملفات إلا إذا تم فتحها بعلامة O_CLOEXEC في الوقت الذي تم فيه تنفيذ الاستدعاء المفتوح. في حالتنا ، بعد تشغيل الملفات القابلة للتنفيذ ، سيتم حفظ جميع واصفات الملفات الحالية.

نتحقق في وحدة التحكم:

[user@localhost ]$ pgrep -P 15771
9004
9005
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user  0 Oct  7 15:42 .
dr-xr-xr-x 9 user user  0 Oct  7 15:42 ..
lrwx------ 1 user user 64 Oct  7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:42 255 -> /dev/pts/21
[user@localhost ]$ ls -lah /proc/9004/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lrwx------ 1 user user 64 Oct  7 15:57 0 -> /dev/pts/21
l-wx------ 1 user user 64 Oct  7 15:57 1 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
lr-x------ 1 user user 64 Oct  7 15:57 3 -> /dev/zero
[user@localhost ]$ ls -lah /proc/9005/fd
total 0
dr-x------ 2 user user  0 Oct  7 15:57 .
dr-xr-xr-x 9 user user  0 Oct  7 15:57 ..
lr-x------ 1 user user 64 Oct  7 15:57 0 -> pipe:[253543032]
lrwx------ 1 user user 64 Oct  7 15:57 1 -> /dev/pts/21
lrwx------ 1 user user 64 Oct  7 15:57 2 -> /dev/pts/21
[user@localhost ]$ ps -up 9004
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9004  0.0  0.0 107972   620 pts/21   S+   15:57   0:00 cat /dev/zero
[user@localhost ]$ ps -up 9005
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
user  9005  0.0  0.0 107952   360 pts/21   S+   15:57   0:00 sleep 10000

كما ترى ، فإن الرقم الفريد لأنبوبنا هو نفسه في كلتا العمليتين. وبالتالي ، لدينا صلة بين عمليتين مختلفتين مع نفس الوالد.

بالنسبة لأولئك الذين ليسوا على دراية باستدعاءات النظام التي يستخدمها bash ، أوصي بشدة بتشغيل الأوامر من خلال strace ومعرفة ما يحدث في الداخل ، على سبيل المثال ، مثل هذا:

strace -s 1024 -f bash -c "ls | grep hello"

دعنا نعود إلى مشكلتنا مع نفاد مساحة القرص ومحاولة حفظ البيانات دون إعادة تشغيل العملية. لنكتب برنامجًا صغيرًا يكتب على القرص حوالي 1 ميغا بايت في الثانية. علاوة على ذلك ، إذا لم نتمكن من كتابة البيانات على القرص لسبب ما ، فسنقوم ببساطة بتجاهل ذلك ومحاولة كتابة البيانات مرة أخرى في ثانية. في المثال الذي أستخدم فيه Python ، يمكنك استخدام أي لغة برمجة أخرى.

[user@localhost ]$ cat openforwrite.py 
import datetime
import time

mystr="a"*1024*1024+"n"
with open("123.txt", "w") as f:
    while True:
        try:
            f.write(str(datetime.datetime.now()))
            f.write(mystr)
            f.flush()
            time.sleep(1)
        except:
            pass

قم بتشغيل البرنامج وانظر إلى واصفات الملفات

[user@localhost ]$ python openforwrite.py &
[1] 3762
[user@localhost ]$ ps axuf | grep [o]penforwrite
user  3762  0.0  0.0 128600  5744 pts/22   S+   16:28   0:00  |   _ python openforwrite.py
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt

كما ترى ، لدينا 3 واصفات ملفات قياسية وواحد آخر قمنا بفتحه. دعنا نتحقق من حجم الملف:

[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 117M Oct  7 16:30 123.txt

البيانات مكتوبة ، نحاول تغيير حقوق الملف:

[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 168M Oct  7 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt 
-rw-rw-r-- 1 root root 172M Oct  7 16:31 123.txt

نرى أن البيانات لا تزال قيد الكتابة ، على الرغم من أن مستخدمنا ليس لديه الحق في الكتابة إلى الملف. دعنا نحاول إزالته:

[user@localhost ]$ sudo rm 123.txt 
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory

أين هي البيانات المكتوبة؟ وهل هي مكتوبة أصلا؟ نحن نفحص:

[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user  0 Oct  7 16:29 .
dr-xr-xr-x 9 user user  0 Oct  7 16:29 ..
lrwx------ 1 user user 64 Oct  7 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  7 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  7 16:29 3 -> /home/user/123.txt (deleted)

نعم ، لا يزال واصف الملف لدينا موجودًا ، ويمكننا العمل مع واصف الملف هذا مثل ملفنا القديم ، ويمكننا قراءته وتنظيفه ونسخه.

انظر إلى حجم الملف:

[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5   19923457   2621522 /home/user/123.txt

حجم الملف هو 19923457. محاولة مسح الملف:

[user@localhost ]$ truncate -s 0 /proc/31083/fd/3
[user@localhost ]$ lsof | grep 123.txt
python    31083             user    3w      REG                8,5  136318390   2621522 /home/user/123.txt

كما ترى ، يزيد حجم الملف فقط ولم يعمل صندوقنا. دعنا ننتقل إلى الوثائق الخاصة باستدعاء النظام جاكيت . إذا استخدمنا علامة O_APPEND عند فتح ملف ، فمع كل عملية كتابة ، يتحقق نظام التشغيل من حجم الملف ويكتب البيانات في نهاية الملف ، ويقوم بذلك بشكل تلقائي. هذا يسمح لعدة خيوط أو عمليات للكتابة إلى نفس الملف. لكن في الكود الخاص بنا لا نستخدم هذه العلامة. لا يمكننا رؤية حجم ملف مختلف في lsof بعد trunk إلا إذا فتحنا الملف للكتابة ، مما يعني أنه في الكود الخاص بنا ، بدلاً من

with open("123.txt", "w") as f:

علينا أن نضع

with open("123.txt", "a") as f:

التحقق بعلامة "w"

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

ومع علم ""

[user@localhost ]$ strace -e trace=open python openforwrite.py 2>&1| grep 123.txt
open("123.txt", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

برمجة عملية قيد التشغيل بالفعل

في كثير من الأحيان ، عند إنشاء برنامج واختباره ، يستخدم المبرمجون مصححات الأخطاء (على سبيل المثال ، GDB) أو مستويات مختلفة من تسجيل الدخول إلى التطبيق. يوفر Linux القدرة على كتابة برنامج قيد التشغيل بالفعل وتغييره ، مثل تغيير قيم المتغيرات ، وتعيين نقطة توقف ، وما إلى ذلك.

بالعودة إلى السؤال الأصلي حول نقص مساحة القرص لكتابة ملف ، دعنا نحاول محاكاة المشكلة.

لنقم بإنشاء ملف للقسم الخاص بنا ، والذي سنقوم بتركيبه كمحرك أقراص منفصل:

[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$

لنقم بإنشاء نظام ملفات:

[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd is not a block special device.
Proceed anyway? (y,n) y
...
Writing superblocks and filesystem accounting information: done
[user@localhost ~]$

لنقم بتركيب نظام الملفات:

[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] password for user: 
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  172K  7.9M   3% /mnt

قم بإنشاء دليل مع مالكنا:

[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs

لنفتح الملف للكتابة فقط في برنامجنا:

with open("/mnt/logs/123.txt", "w") as f:

نطلق

[user@localhost ]$ python openforwrite.py 

انتظر بضع ثوان

[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

لذلك ، حصلنا على المشكلة الموضحة في بداية هذه المقالة. مساحة حرة 0 ، مشغولة بنسبة 100٪.

نتذكر أنه وفقًا لظروف المشكلة ، نحاول تسجيل بيانات مهمة جدًا لا يمكن فقدها. وعند القيام بذلك ، نحتاج إلى إصلاح الخدمة دون إعادة تشغيل العملية.

لنفترض أنه لا يزال لدينا مساحة على القرص ، ولكن في قسم مختلف ، على سبيل المثال ، في / home.

دعنا نحاول "إعادة برمجة على الطاير" كودنا.

نحن ننظر إلى PID لعمليتنا ، والتي استهلكت كل مساحة القرص:

[user@localhost ~]$ ps axuf | grep [o]penfor
user 10078 27.2  0.0 128600  5744 pts/22   R+   11:06   0:02  |   _ python openforwrite.py

الاتصال بعملية مع gdb

[user@localhost ~]$ gdb -p 10078
...
(gdb) 

ننظر إلى واصفات الملفات المفتوحة:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt

نحن ننظر إلى معلومات حول واصف الملف برقم 3 ، والتي تهمنا

(gdb) shell cat /proc/10078/fdinfo/3
pos:    8189952
flags:  0100001
mnt_id: 482

مع الأخذ في الاعتبار ما يفعله نظام Python (انظر أعلاه حيث قمنا بتشغيل strace ووجدنا مكالمة مفتوحة) ، أثناء معالجة الكود الخاص بنا لفتح ملف ، نفعل الشيء نفسه نيابة عن عمليتنا ، لكننا نحتاج إلى O_WRONLY | O_CREAT | يتم استبدال بتات O_TRUNC بقيمة رقمية. للقيام بذلك ، افتح مصادر kernel ، على سبيل المثال هنا ونرى أي الأعلام هي المسؤولة عن ماذا

#تعريف O_WRONLY 00000001
#تعريف O_CREAT 00000100
# تعريف O_TRUNC 00001000

نحن نجمع كل القيم في واحد ، نحصل على 00001101

تشغيل مكالمتنا من gdb

(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4

لذلك حصلنا على واصف ملف جديد برقم 4 وملف مفتوح جديد على قسم آخر ، تحقق من:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

نتذكر مثال الأنبوب - كيف يغير bash واصفات الملفات ، وتعلمنا بالفعل استدعاء نظام dup2.

محاولة استبدال واصف ملف بآخر

(gdb) call dup2(4,3)
$2 = 3

نتحقق من:

(gdb) shell ls -lah /proc/10078/fd/
total 0
dr-x------ 2 user user  0 Oct  8 11:06 .
dr-xr-xr-x 9 user user  0 Oct  8 11:06 ..
lrwx------ 1 user user 64 Oct  8 11:09 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:09 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:06 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:09 3 -> /home/user/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

أغلق ملف واصف 4 ، لأننا لسنا بحاجة إليه:

(gdb) call close (4)
$1 = 0

والخروج من gdb

(gdb) quit
A debugging session is active.

    Inferior 1 [process 10078] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 10078

التحقق من الملف الجديد:

[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 5.1M Oct  8 11:18 /home/user/123.txt
[user@localhost ~]$ ls -lah /home/user/123.txt
-rw-rw-r-- 1 user user 7.1M Oct  8 11:18 /home/user/123.txt

كما ترى ، تتم كتابة البيانات في ملف جديد ، نتحقق من الملف القديم:

[user@localhost ~]$ ls -lah /mnt/logs/123.txt 
-rw-rw-r-- 1 user user 7.9M Oct  8 11:08 /mnt/logs/123.txt

لا تضيع البيانات ، يعمل التطبيق ، تتم كتابة السجلات في موقع جديد.

لنجعل الأمور أكثر صعوبة

تخيل أن البيانات مهمة بالنسبة لنا ، ولكن ليس لدينا مساحة على القرص في أي من الأقسام ولا يمكننا توصيل القرص.

ما يمكننا القيام به هو إعادة توجيه بياناتنا في مكان ما ، على سبيل المثال ، إلى أنبوب ، وإعادة توجيه البيانات من الأنبوب إلى الشبكة من خلال بعض البرامج ، مثل netcat.
يمكننا إنشاء أنبوب مسمى باستخدام الأمر mkfifo. سيقوم بإنشاء ملف زائف على نظام الملفات ، حتى لو لم يكن هناك مساحة خالية عليه.

أعد تشغيل التطبيق وتحقق من:

[user@localhost ]$ python openforwrite.py 
[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 72.9  0.0 128600  5744 pts/22   R+   11:27   0:20  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

لا توجد مساحة على القرص ، ولكننا نجحنا في إنشاء أنبوب مسمى هناك:

[user@localhost ~]$ mkfifo /mnt/logs/megapipe
[user@localhost ~]$ ls -lah /mnt/logs/megapipe 
prw-rw-r-- 1 user user 0 Oct  8 11:28 /mnt/logs/megapipe

نحتاج الآن إلى التفاف جميع البيانات التي تدخل في هذا الأنبوب بطريقة ما إلى خادم آخر عبر الشبكة ، فإن netcat نفسها مناسبة لذلك.

على خادم remote-server.example.com ، قم بتشغيل

[user@localhost ~]$ nc -l 7777 > 123.txt 

على خادم المشكلة الخاص بنا ، قم بتشغيله في محطة منفصلة

[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

الآن ستنتقل جميع البيانات التي تدخل الأنبوب تلقائيًا إلى stdin في netcat ، والتي سترسلها إلى الشبكة على المنفذ 7777.

كل ما علينا فعله هو البدء في كتابة بياناتنا إلى هذا الأنبوب المسمى.

لدينا بالفعل تطبيق قيد التشغيل:

[user@localhost ~]$ ps axuf | grep [o]pen
user  5946 99.8  0.0 128600  5744 pts/22   R+   11:27 169:27  |   _ python openforwrite.py
[user@localhost ~]$ ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt

من بين جميع العلامات ، نحتاج فقط إلى O_WRONLY لأن الملف موجود بالفعل ولسنا بحاجة إلى مسحه

[user@localhost ~]$ gdb -p 5946
...
(gdb) call open("/mnt/logs/megapipe", 00000001,0666)
$1 = 4
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call dup2(4,3)
$2 = 3
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
l-wx------ 1 user user 64 Oct  8 14:20 4 -> /mnt/logs/megapipe
(gdb) call close(4)
$3 = 0
(gdb) shell ls -lah /proc/5946/fd
total 0
dr-x------ 2 user user  0 Oct  8 11:27 .
dr-xr-xr-x 9 user user  0 Oct  8 11:27 ..
lrwx------ 1 user user 64 Oct  8 11:28 0 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:28 1 -> /dev/pts/22
lrwx------ 1 user user 64 Oct  8 11:27 2 -> /dev/pts/22
l-wx------ 1 user user 64 Oct  8 11:28 3 -> /mnt/logs/megapipe
(gdb) quit
A debugging session is active.

    Inferior 1 [process 5946] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2.7, process 5946

التحقق من الخادم البعيد remote-server.example.com

[user@localhost ~]$ ls -lah 123.txt 
-rw-rw-r-- 1 user user 38M Oct  8 14:21 123.txt

البيانات قادمة ، نتحقق من خادم المشكلة

[user@localhost ~]$ ls -lah /mnt/logs/
total 7.9M
drwxr-xr-x 2 user user 1.0K Oct  8 11:28 .
drwxr-xr-x 4 root     root     1.0K Oct  8 10:55 ..
-rw-rw-r-- 1 user user 7.9M Oct  8 14:17 123.txt
prw-rw-r-- 1 user user    0 Oct  8 14:22 megapipe

يتم حفظ البيانات ، تم حل المشكلة.

أغتنم هذه الفرصة لألقي التحية على زملائي من ديجيرو.
استمع إلى راديو تي بودكاست.

الامور جيدة.

كواجب منزلي ، أقترح التفكير فيما سيكون موجودًا في واصفات ملفات القط وعملية النوم إذا قمت بتشغيل الأمر التالي:

[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000

المصدر: www.habr.com

إضافة تعليق