Файл дэскрыптар у Linux з прыкладамі

Аднойчы, на адным інтэрв'ю мяне спыталі, што ты будзеш рабіць, калі выявіш непрацуючы сэрвіс праз тое, што на дыску скончылася месца?

Вядома ж я адказаў, што пагляджу, чым занята гэтае месца і калі магчыма, то пачышчу месца.
Тады інтэрв'юер спытаў, а што калі на раздзеле няма вольнага месца, але і файлаў, якія б займалі ўсё месца, ты таксама не бачыш?

На гэта я сказаў, што заўсёды можна паглядзець адчыненыя файл дэскрыптары, напрыклад камандай lsof і зразумець якое прыкладанне заняло ўсё даступнае месца, а далей можна дзейнічаць па абставінах, у залежнасці ад таго, ці патрэбныя дадзеныя.

Інтэрв'юер перапыніў мяне на апошнім слове, дапоўніўшы сваё пытанне: «Выкажам здагадку, што дадзеныя нам не патрэбныя, гэта проста дэбаг лог, але прыкладанне не працуе з-за таго, што не можа запісаць дэбаг»?

"окей", - адказаў я, "мы можам выключыць дэбаг у канфігу прыкладання і перазапусціць яго".
Інтэрв'юер запярэчыў: "Не, прыкладанне мы перазапусціць не можам, у нас у памяці ўсё яшчэ захоўваюцца важныя дадзеныя, а да самога сэрвісу падключаны важныя кліенты, якіх мы не можам прымушаць перападключаць зноўку".

"ну добра", сказаў я, "калі мы не можам перазапускаць прыкладанне і дадзеныя нам не важныя, то мы можам проста ачысціць гэты адчынены файл праз файл дэскрыптар, нават калі мы яго не бачым у камандзе ls на файлавай сістэме".

Інтэрв'юер застаўся задаволены, а я не.

Тады я падумаў, чаму чалавек, які правярае мае веды, не капае глыбей? А што, калі дадзеныя ўсё ж такі важныя? Што калі мы не можам перазапускаць працэс, і пры гэтым гэты працэс піша на файлавую сістэму ў падзел, на якім няма вольнага месца? Што калі мы не можам страціць не толькі ўжо запісаныя дадзеныя, але і тыя дадзеныя, што гэты працэс піша ці спрабуе запісаць?

Тузік

У пачатку маёй кар'еры я спрабаваў стварыць невялікае дадатак, у якім трэба было захоўваць інфармацыю аб карыстальніках. І тады я думаў, а як мне супаставіць карыстальніка да ягоных дадзеных. Ёсць, напрыклад, у мяне Іваноў Іван Іванавіч, і ёсць у яго нейкія дадзеныя, але як іх пасябраваць? Я магу ўказаць наўпрост, што сабака па імені «Тузік» належыць гэтаму самому Івану. Але што, калі ён зменіць імя і замест Івана стане, напрыклад, Оляй? Тады атрымаецца, што наша Вольга Іванаўна Іванова больш не будзе мець сабакі, а наш Тузік усё яшчэ будзе належаць неіснуючаму Івану. Вырашыць гэтую праблему дапамагла база дадзеных, якая кожнаму карыстачу давала ўнікальны ідэнтыфікатар (ID), і мой Тузік прывязваўся да гэтага ID, які, у сутнасці, быў проста парадкавым нумарам. Такім чынам гаспадар у тузіка быў з ID пад нумарам 2, і на нейкі момант часу пад гэтым ID быў Іван, а потым пад гэтым жа ID стала Оля. Праблема чалавецтва і жывёлагадоўлі была практычна вырашана.

Файл дэскрыптар

Праблема файла і праграмы, якая працуе з гэтым файлам, прыкладна такая ж як нашага сабакі і чалавека. Выкажам здагадку я адкрыў файл пад імем ivan.txt і пачаў у яго запісваць слова tuzik, але паспеў запісаць толькі першую літару «t» у файл, і гэты файл быў кімсьці пераназваны, напрыклад у olya.txt. Але файл застаўся тым жа самым, і я ўсё яшчэ жадаю запісаць у яго свайго тузіка. Кожны раз пры адкрыцці файла сістэмным выклікам адкрыць у любой мове праграмавання я атрымліваю унікальны ID, які паказвае мне на файл, гэты ID і ёсць файл дэскрыптар. І зусім не важна, што і хто робіць з гэтым файлам далей, яго могуць выдаліць, яго могуць пераназваць, яму могуць памяняць уладальніка ці забраць правы на чытанне і запіс, я ўсё роўна буду мець да яго доступ, таму што на момант адкрыцця файла ў мяне мелі правы для яго чытання і/ці запісы і я паспеў пачаць з ім працаваць, а значыць павінен працягваць гэта рабіць.

У Linux бібліятэка libc адчыняе для кожнага запушчанага прыкладання(працэсу) 3 файл дэскрыптара, з нумарамі 0,1,2. Больш інфармацыі вы можаце знайсці па спасылках man stdio и man stdout

  • Файл дэскрыптар 0 завецца STDIN і асацыюецца з уводам дадзеных у прыкладання
  • Файл дэскрыптар 1 завецца STDOUT і выкарыстоўваецца прыкладаннямі для вываду дадзеных, напрыклад камандамі print
  • Файл дэскрыптар 2 завецца STDERR і выкарыстоўваецца прыкладаннямі для вываду дадзеных, якія паведамляюць пра памылку.

Калі ў вашай праграме вы адкрыеце які-небудзь файл на чытанне ці запіс, то хутчэй за ўсё вы атрымаеце першы вольны ID і гэта будзе нумар 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, а не прылінкаванай бібліятэкай.

Цяпер усе 3 файл дэскрыптара звязаныя з прыладай псеўдатэрмінала /dev/pts, але мы ўсё роўна можам імі маніпуляваць, напрыклад запусцім у другой кансолі

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

І ў першай кансолі мы ўбачым

[user@localhost ]$ hello world

Redirect і Pipe

Вы можаце лёгка перавызначыць гэтыя 3 файл дэскрыптара ў любым працэсе, у тым ліку і ў bash, напрыклад праз трубу(pipe), якая злучае два працэсы, глядзім

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

Вы можаце самі запусціць гэтую каманду з strace -f і ўбачыць, што адбываецца ўнутры, але я коратка раскажу.

Наш бацькоўскі працэс bash з PID 15771 парсіць нашу каманду і разумее колькі менавіта каманд мы жадаем запусціць, у нашым выпадку іх дзве: cat і sleep. Bash ведае, што яму трэба стварыць два даччыных працэсу, і аб'яднаць іх адной трубой. Разам bash спатрэбіцца 2 даччыных працэсу і XNUMX pipe.

Перад стварэннем даччыных працэсаў bash запускае сістэмны выклік труба і атрымлівае новыя файл дэскрыптары на часовы буфер pipe, але гэты буфер ніяк пакуль не злучае нашы два даччыных працэсу.

Для бацькоўскага працэсу гэта выглядае так быццам pipe ужо ёсць, а даччыных працэсаў яшчэ няма:

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

Не забываем, што clone клануе працэс разам са ўсімі файл дэскрыптарамі, таму ў бацькоўскім працэсе і ў даччыных яны будуць аднолькавыя. Задача бацькоўскага працэсу з PID 15771 сачыць за даччынымі працэсамі, таму ён проста чакае адказ ад даччыных.

Такім чынам pipe яму не патрэбен, і ён зачыняе файл дэскрыптары з нумарамі 3 і 4.

У першым даччыным працэсе bash з PID 9004, сістэмным выклікам dup2, змяняе наш STDOUT файл дэскрыптар з нумарам 1 на файл дэскрыптар які паказвае на pipe, у нашым выпадку гэта нумар 3. Такім чынам усё, што першы даччыны працэс з PID 9004 будзе пісаць у STDOUT, будзе аўтаматычна трапляць у буфер pipe.

У другім даччыным працэсе з PID 9005 bash мяняе з дапамогай dup2 файл дэскрыптар STDIN з нумарам 0. Цяпер усё, што будзе чытаць наш другі bash з PID 9005, будзе чытаць з pipe.

Пасля гэтага ў даччыных працэсах гэтак жа зачыняюцца файл дэскрыптары з нумарамі 3 і 4, бо яны больш не выкарыстоўваюцца.

Файл дэскрыптар 255 я наўмысна ігнарую, ён выкарыстоўвае для ўнутраных патрэб самога bash і ў даччыных працэсах будзе таксама зачынены.

Далей у першым даччыным працэсе з PID 9004 bash запускае з дапамогай сістэмнага выкліку Exec выкананы файл, які мы паказалі ў камандным радку, у нашым выпадку гэта /usr/bin/cat.

У другім даччыным працэсе з PID 9005 bash запускае другі выкананы файл, які мы паказалі, у нашым выпадку гэта /usr/bin/sleep.

Сістэмны выклік exec не закрывае файл дэскрыптары, калі яны не былі адчыненыя са сцягам O_CLOEXEC падчас выканання выкліку open. У нашым выпадку пасля запуску выкананых файлаў усе бягучыя файл дэскрыптары захаваюцца.

Правяраем у кансолі:

[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

Як бачыце ўнікальны нумар нашага pipe у нас у абодвух працэсах супадае. Такім чынам, у нас ёсць сувязь паміж двума рознымі працэсамі з адным з бацькоў.

Для тых, хто не знаёмы з сістэмнымі выклікамі, якія выкарыстоўвае 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 пасля транкейт толькі калі адкрыем файл для дазапісу, а значыць у нашым кодзе замест

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

і з «a» сцягам

[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 дае магчымасць фактычна пісаць і змяняць ужо запушчаную праграму, напрыклад мяняць значэнні зменных, усталёўваць breakpoint і тд і тп.

Вяртаючыся да арыгінальнага пытання з недахопам месца на дыску для запісу файла, паспрабуем сэмуляваць праблему.

Створым файл для нашай часткі, які мы падмантаваны як асобная кружэлка:

[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 і знаходзілі выклік open), апрацоўваючы наш код для адкрыцця файла, мы робім тое ж самае самастойна ад імя нашага працэсу, але біты O_WRONLY|O_CREAT|O_TRUNC нам трэба замяніць на лікавае значэнне. Для гэтага адкрываем зыходнікі ядра, напрыклад тут і глядзім якія сцягі за што адказваюць

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define 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

Мы памятаем прыклад з pipe - як 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

Дадзеныя не страчаныя, прыкладанне працуе, логі пішуцца ў новае месца.

Трохі ўскладнім задачу

Уявім, што дадзеныя нам важныя, але месца на дыску ў нас няма ні ў адным з раздзелаў і падключыць дыск мы не можам.

Што мы можам зрабіць, дык гэта перанакіраваць кудысьці нашы дадзеныя, напрыклад у pipe, а дадзеныя з pipe у сваю чаргу перанакіраваць у сетку праз якую-небудзь праграму, напрыклад netcat.
Мы можам стварыць найменны pipe камандай 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

Месца на дыску няма, але мы паспяхова ствараем там найменны pipe:

[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

Цяпер нам трэба неяк загарнуць усе дадзеныя, што пападаюць у гэты pipe на іншы сервер праз сетку, для гэтага падыдзе ўсё той жа netcat.

На сэрвэры remote-server.example.com запускаем

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

На нашым праблемным серверы запускаем у асобным тэрмінале

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

Зараз усе дадзеныя, якія патрапяць у pipe аўтаматычна патрапяць на stdin у netcat, які іх адправіць у сетку на порт 7777.

Усё, што нам засталося зрабіць гэта пачаць пісаць нашы дадзеныя ў гэты найменны pipe.

У нас ужо ёсць запушчанае прыкладанне:

[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

Дадзеныя захаваліся, праблема вырашана.

Карыстаючыся выпадкам, перадаю прывітанне калегам з кампаніі Degiro.
Слухайце падкасты Радыё-Т.

Усім дабра.

У якасці хатняга задання прапаную падумаць, што будзе ў файл дэскрыптарах працэсу cat і sleep калі запусціць такую ​​каманду:

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

Крыніца: habr.com

Дадаць каментар