Дескриптор Файла В Linux С Примерами

Однажды во время собеседования меня спросили, что вы будете делать, если обнаружите, что сервис не работает из-за того, что на диске закончилось место? Я, конечно, ответил, что посмотрю, чем занято это место, и, если возможно, приберу это место.

Тогда интервьюер спросил, а что если на разделе нет свободного места, но и файлов, которые бы занимали все место, вы также не видите? На это я сказал, что всегда можно посмотреть дескрипторы открытых файлов, например командой lsof, и понять, какое приложение заняло все доступное место, а дальше можно действовать по обстоятельствам, в зависимости от того, нужны ли данные .

Интервьюер прервал меня на последнем слове, добавив к своему вопросу: «Предположим, данные нам не нужны, это просто лог отладки, но приложение не работает, потому что не умеет писать отладку»? «Хорошо, — ответил я, — мы можем отключить отладку в конфиге приложения и перезапустить его».

Интервьюер возразил: «Нет, мы не можем перезапустить приложение, у нас еще важные данные хранятся в памяти, а важные клиенты подключены к самому сервису, который мы не можем принудительно переподключить снова».

«окей, — сказал я, — если мы не можем перезапустить приложение и данные нам не важны, то мы можем просто очистить этот открытый файл через файловый дескриптор, даже если мы его не видим в команде ls» в файловой системе».

Интервьюер был доволен, а я нет. Тогда я подумал, а почему человек, проверяющий мои знания, не копает глубже? Но что, если данные все-таки важны? Что, если мы не можем перезапустить процесс, и он записывает данные в файловую систему раздела, в котором нет свободного места? Что если мы не можем потерять не только те данные, которые уже были записаны, но и данные, которые этот процесс пишет или пытается записать?



Тузик

В начале своей карьеры я пытался создать небольшое приложение, которое должно было хранить информацию о пользователях.

И тогда я подумал, как мне сопоставить пользователя с его данными.

Например, у меня есть Иванов Иван Иванович, и у него есть некоторая информация, но как мне с ними подружиться? Могу прямо указать, что собака по кличке Тузик принадлежит этому самому Ивану.

А что, если он поменяет имя и вместо Ивана станет, например, Олей? Тогда окажется, что у нашей Оли Ивановны Ивановой собаки уже не будет, а наш Тузик по-прежнему будет принадлежать несуществующему Ивану.

Решить эту проблему помогла база данных, которая давала каждому пользователю уникальный идентификатор (ID), и мой Тузик был привязан к этому идентификатору, который, по сути, был просто серийным номером.

Таким образом, у владельца туза был ID номер 2, и под этим ID в какой-то момент времени находился Иван, а затем под этим же ID стала Оля.

Проблема человечества и животноводства была практически решена.



Дескриптор файла

Проблема файла и программы, работающей с этим файлом, примерно такая же, как у нашей собаки и человека.

Допустим, я открыл файл ivan.txt и начал писать в него слово тузик, но успел написать в файле только первую букву «т», а этот файл был кем-то переименован, например, в olya.txt. Но файл остаётся прежним, и я всё равно хочу записать в него свой эйс.

Каждый раз, когда файл открывается системным вызовом открыть на любом языке программирования я получаю уникальный идентификатор, указывающий на файл, этот идентификатор является дескриптором файла.

И совершенно не важно, что и кто делает с этим файлом дальше, его можно удалить, можно переименовать, можно сменить владельца или отобрать права на чтение и запись, доступ у меня все равно останется.

к нему, потому что на момент открытия файла у меня были права на его чтение и/или запись и мне удалось начать с ним работать, а значит, я должен продолжать это делать.

В Linux библиотека libc открывает 3 файла дескриптора для каждого запущенного приложения (процесса) с номерами 0,1,2. Более подробную информацию можно найти по ссылкам мужская студия И человек стандартный вывод

  • Дескриптор файла 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, а не связанной библиотекой.

Теперь все 3 файла дескриптора связаны с псевдотерминальным устройством.

/dev/птс , но мы все равно можем ими манипулировать, например запускать их во второй консоли

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

И в первой консоли мы увидим

[user@localhost ]$ hello world



Перенаправление и канал

Вы можете легко переопределить эти 3 файла дескриптора в любом процессе, в том числе в bash, например через канал, соединяющий два процесса, см.



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

Вы можете запустить эту команду самостоятельно с помощью строка -f и посмотреть, что происходит внутри, но я расскажу коротко.

Наш родительский процесс bash с PID 15771 анализирует нашу команду и точно понимает, сколько команд мы хотим выполнить, в нашем случае их две: cat и Sleep. Bash знает, что ему нужно создать два дочерних процесса и объединить их в один канал.

Всего bash понадобится 2 дочерних процесса и один канал.

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

Не забывайте, что clone клонирует процесс вместе со всеми файловыми дескрипторами, поэтому они будут одинаковыми и в родительском процессе, и в дочерних.

Задача родительского процесса с PID 15771 — следить за дочерними процессами, поэтому он просто ожидает ответа от дочерних процессов.

Поэтому ему не нужен канал, и он закрывает файловые дескрипторы с номерами 3 и 4. В первом дочернем процессе bash с PID 9004 системный вызов дубликат2 , меняет наш файловый дескриптор STDOUT с номером 1 на файловый дескриптор, указывающий на канал, в нашем случае это номер 3. Таким образом, все, что первый дочерний процесс с PID 9004 записывает в STDOUT, автоматически попадает в буфер канала.

Во втором дочернем процессе с PID 9005 bash использует dup2 для изменения файлового дескриптора STDIN с номером 0. Теперь все, что будет читать наш второй bash с PID 9005, будет читаться из канала.

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

Я намеренно игнорирую файловый дескриптор 255; он используется для внутренних целей самим bash и также будет закрыт в дочерних процессах.

Далее, в первом дочернем процессе с PID 9004, bash запускается с помощью системного вызова.

руководитель исполняемый файл, который мы указали в командной строке, в нашем случае это /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 после транка, только если откроем файл для дополнительной записи, то есть в нашем коде вместо этого

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 и нашли вызов 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

Данные не теряются, приложение работает, логи пишутся в новое место.



Немного усложним задачу

Представим, что нам важны данные, но у нас нет места на диске ни в одном из разделов и мы не можем подключить диск.

Что мы можем сделать, так это перенаправить наши данные куда-нибудь, например в канал, и в свою очередь перенаправить данные из канала в сеть через какую-нибудь программу, например 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

Теперь все данные, попадающие в канал, будут автоматически поступать на стандартный ввод в 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

Данные сохранены, проблема решена.

Пользуясь случаем, передаю привет моим коллегам из Дегиро.

Слушайте подкасты Радио-Т.

Всего наилучшего.

В качестве домашнего задания предлагаю вам подумать, что будет в дескрипторах файлов процесса cat и Sleep, если вы выполните следующую команду:

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

Теги: #linux #*nix #python #Администрирование сервера #Системное программирование #дескрипторы

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.