Аднойчы, на адным інтэрв'ю мяне спыталі, што ты будзеш рабіць, калі выявіш непрацуючы сэрвіс праз тое, што на дыску скончылася месца?
Вядома ж я адказаў, што пагляджу, чым занята гэтае месца і калі магчыма, то пачышчу месца.
Тады інтэрв'юер спытаў, а што калі на раздзеле няма вольнага месца, але і файлаў, якія б займалі ўсё месца, ты таксама не бачыш?
На гэта я сказаў, што заўсёды можна паглядзець адчыненыя файл дэскрыптары, напрыклад камандай lsof і зразумець якое прыкладанне заняло ўсё даступнае месца, а далей можна дзейнічаць па абставінах, у залежнасці ад таго, ці патрэбныя дадзеныя.
Інтэрв'юер перапыніў мяне на апошнім слове, дапоўніўшы сваё пытанне: «Выкажам здагадку, што дадзеныя нам не патрэбныя, гэта проста дэбаг лог, але прыкладанне не працуе з-за таго, што не можа запісаць дэбаг»?
"окей", - адказаў я, "мы можам выключыць дэбаг у канфігу прыкладання і перазапусціць яго".
Інтэрв'юер запярэчыў: "Не, прыкладанне мы перазапусціць не можам, у нас у памяці ўсё яшчэ захоўваюцца важныя дадзеныя, а да самога сэрвісу падключаны важныя кліенты, якіх мы не можам прымушаць перападключаць зноўку".
"ну добра", сказаў я, "калі мы не можам перазапускаць прыкладанне і дадзеныя нам не важныя, то мы можам проста ачысціць гэты адчынены файл праз файл дэскрыптар, нават калі мы яго не бачым у камандзе ls на файлавай сістэме".
Інтэрв'юер застаўся задаволены, а я не.
Тады я падумаў, чаму чалавек, які правярае мае веды, не капае глыбей? А што, калі дадзеныя ўсё ж такі важныя? Што калі мы не можам перазапускаць працэс, і пры гэтым гэты працэс піша на файлавую сістэму ў падзел, на якім няма вольнага месца? Што калі мы не можам страціць не толькі ўжо запісаныя дадзеныя, але і тыя дадзеныя, што гэты працэс піша ці спрабуе запісаць?
Тузік
У пачатку маёй кар'еры я спрабаваў стварыць невялікае дадатак, у якім трэба было захоўваць інфармацыю аб карыстальніках. І тады я думаў, а як мне супаставіць карыстальніка да ягоных дадзеных. Ёсць, напрыклад, у мяне Іваноў Іван Іванавіч, і ёсць у яго нейкія дадзеныя, але як іх пасябраваць? Я магу ўказаць наўпрост, што сабака па імені «Тузік» належыць гэтаму самому Івану. Але што, калі ён зменіць імя і замест Івана стане, напрыклад, Оляй? Тады атрымаецца, што наша Вольга Іванаўна Іванова больш не будзе мець сабакі, а наш Тузік усё яшчэ будзе належаць неіснуючаму Івану. Вырашыць гэтую праблему дапамагла база дадзеных, якая кожнаму карыстачу давала ўнікальны ідэнтыфікатар (ID), і мой Тузік прывязваўся да гэтага ID, які, у сутнасці, быў проста парадкавым нумарам. Такім чынам гаспадар у тузіка быў з ID пад нумарам 2, і на нейкі момант часу пад гэтым ID быў Іван, а потым пад гэтым жа ID стала Оля. Праблема чалавецтва і жывёлагадоўлі была практычна вырашана.
Файл дэскрыптар
Праблема файла і праграмы, якая працуе з гэтым файлам, прыкладна такая ж як нашага сабакі і чалавека. Выкажам здагадку я адкрыў файл пад імем ivan.txt і пачаў у яго запісваць слова tuzik, але паспеў запісаць толькі першую літару «t» у файл, і гэты файл быў кімсьці пераназваны, напрыклад у olya.txt. Але файл застаўся тым жа самым, і я ўсё яшчэ жадаю запісаць у яго свайго тузіка. Кожны раз пры адкрыцці файла сістэмным выклікам
У Linux бібліятэка libc адчыняе для кожнага запушчанага прыкладання(працэсу) 3 файл дэскрыптара, з нумарамі 0,1,2. Больш інфармацыі вы можаце знайсці па спасылках
- Файл дэскрыптар 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 файл дэскрыптара звязаныя з прыладай псеўдатэрмінала
[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 ужо ёсць, а даччыных працэсаў яшчэ няма:
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
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, сістэмным выклікам
У другім даччыным працэсе з PID 9005 bash мяняе з дапамогай dup2 файл дэскрыптар STDIN з нумарам 0. Цяпер усё, што будзе чытаць наш другі bash з PID 9005, будзе чытаць з pipe.
Пасля гэтага ў даччыных працэсах гэтак жа зачыняюцца файл дэскрыптары з нумарамі 3 і 4, бо яны больш не выкарыстоўваюцца.
Файл дэскрыптар 255 я наўмысна ігнарую, ён выкарыстоўвае для ўнутраных патрэб самога bash і ў даччыных працэсах будзе таксама зачынены.
Далей у першым даччыным працэсе з PID 9004 bash запускае з дапамогай сістэмнага выкліку
У другім даччыным працэсе з 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
Як бачым памер файла толькі павялічваецца і наш транкейт не спрацаваў. Звернемся да дакументацыі па сістэмным выкліку
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