Я, как, вероятно, и многие другие пользователи Linux, привык сохранять видео с сайтов вроде YouTube, копируя временные файлы, создаваемые Adobe Flash. Примерно так:
$ cp /tmp/FlashIBmQCU video.flv
Поставив свежий пре-релиз флэш-плеера, который на днях обсуждали на хабре (у меня 64-битная система), я с удивлением обнаружил, что этот способ больше не работает, так как никакие файлы во временном каталоге не создаются. Рассудив, однако, что едва ли плеер хранит видео целиком в памяти, и куда-то он его всё-таки должен писать, я приступил к расследованию.
…То есть решил посмотреть список открытых плагином файлов. Для начала нам нужен PID процесса, в котором хостится плагин. Пользуюсь я Firefox-ом, так что искать будем просто:
$ ps x | grep firefox
9800 ? S 0:00 /bin/sh /usr/lib/firefox-3.6.9/firefox
9805 ? S 0:00 /bin/sh /usr/lib/firefox-3.6.9/run-mozilla.sh /usr/lib/firefox-3.6.9/firefox-bin
9809 ? Sl 14:58 /usr/lib/firefox-3.6.9/firefox-bin
10099 ? Sl 4:10 /usr/lib/firefox-3.6.9/plugin-container /usr/lib/mozilla/plugins/libflashplayer.so 9809 plugin
26199 pts/13 S+ 0:00 grep firefox
Как видно, Mozilla запускает плагин в отдельном процессе и его PID — 10099. Теперь список открытых файлов можно посмотреть командой lsof
:
$ lsof -p 10099
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
# поскипано 152 строки
plugin-co 10099 rooslan mem REG 8,21 26048 2656 /usr/lib/gconv/gconv-modules.cache
plugin-co 10099 rooslan mem REG 8,21 343 106080 /usr/lib/locale/ru_RU.utf8/LC_IDENTIFICATION
plugin-co 10099 rooslan 0r CHR 1,3 3419 /dev/null
plugin-co 10099 rooslan 1w FIFO 0,6 9649 pipe
plugin-co 10099 rooslan 2w FIFO 0,6 9649 pipe
plugin-co 10099 rooslan 3u unix 0xffff88007304a3c0 287192 socket
plugin-co 10099 rooslan 4r 0000 0,7 0 32 anon_inode
plugin-co 10099 rooslan 5w unix 0xffff8800c5425e40 287277 socket
plugin-co 10099 rooslan 6r unix 0xffff8800c5424b00 287278 socket
plugin-co 10099 rooslan 7w FIFO 0,6 287279 pipe
plugin-co 10099 rooslan 8r FIFO 0,6 287279 pipe
plugin-co 10099 rooslan 9w FIFO 0,6 287280 pipe
plugin-co 10099 rooslan 10u FIFO 0,6 287280 pipe
plugin-co 10099 rooslan 11u FIFO 0,6 287281 pipe
plugin-co 10099 rooslan 12u FIFO 0,6 287281 pipe
plugin-co 10099 rooslan 13u unix 0xffff88007304a100 287284 socket
plugin-co 10099 rooslan 14u REG 8,24 376832 1409239 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/cert8.db
plugin-co 10099 rooslan 15w REG 8,24 16384 1409240 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/key3.db
plugin-co 10099 rooslan 16u REG 8,23 494641 16 /tmp/FlashXXlm7mcU (deleted)
plugin-co 10099 rooslan 17u FIFO 0,6 404625 pipe
plugin-co 10099 rooslan 18u FIFO 0,6 404625 pipe
plugin-co 10099 rooslan 19r FIFO 0,6 404626 pipe
plugin-co 10099 rooslan 20w FIFO 0,6 404626 pipe
plugin-co 10099 rooslan 21r unix 0xffff880015a2b9c0 404630 socket
Всё самое интересное оказалось в конце и сразу перед глазами, но для порядка попробуем отфильтровать открытые процессом обычные (regular) файлы. Вероятно, это можно сделать встроенными средствами lsof
, но размеры man lsof
быстро отбивают желание читать его для решения такой проходной задачи. Поэтому я предпочёл воспользоваться простым фильтром на AWK:
$ lsof -p 10099 | awk '$4 ~ /^[0-9]+/ && $5 == "REG"'
plugin-co 10099 rooslan 14u REG 8,24 376832 1409239 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/cert8.db
plugin-co 10099 rooslan 15w REG 8,24 16384 1409240 /home/rooslan/.mozilla/firefox/xxxxxxxx.default/key3.db
plugin-co 10099 rooslan 16u REG 8,23 494641 16 /tmp/FlashXXlm7mcU (deleted)
Сразу стало понятно, куда делся наш временный файл: плагин удалил (unlink) ссылку на файл из каталога, но оставил открытым его дескриптор. Таким образом файл перестал быть виден в файловой системе, но не исчез, и окончательно удалён он будет только когда закроется последний ссылающийся на него дескриптор.
Но как нам теперь достать содержимое файла, открытого лишь одним процессом? Очень просто, с помощью файловой системы procfs. Каталог /proc/$PID/fd
содержит символьные ссылки на все открытые процессом PID дескрипторы.
$ ls -l /proc/10099/fd
итого 0
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 0 -> /dev/null
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 1 -> pipe:[9649]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 10 -> pipe:[287280]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 11 -> pipe:[287281]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 12 -> pipe:[287281]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 13 -> socket:[287284]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 14 -> /home/rooslan/.mozilla/firefox/xxxxxxxx.default/cert8.db
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 15 -> /home/rooslan/.mozilla/firefox/xxxxxxxx.default/key3.db
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 16 -> /tmp/FlashXXpOdDuF (deleted)
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 17 -> pipe:[396658]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 18 -> pipe:[396658]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 19 -> pipe:[396659]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 2 -> pipe:[9649]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 20 -> pipe:[396659]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 21 -> socket:[396663]
lrwx------ 1 rooslan rooslan 64 2010-09-16 23:56 3 -> socket:[287192]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 4 -> anon_inode:[eventpoll]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 5 -> socket:[287277]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 6 -> socket:[287278]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 7 -> pipe:[287279]
lr-x------ 1 rooslan rooslan 64 2010-09-16 23:56 8 -> pipe:[287279]
l-wx------ 1 rooslan rooslan 64 2010-09-16 23:56 9 -> pipe:[287280]
(Вот, кстати, ещё один способ посмотреть открытые процессом файлы, помимо lsof
).
И, хотя readlink возвращает для некоторых из этих ссылок имена несуществующих файлов, из них можно спокойно читать (если права позволяют), чем мы и воспользуемся:
$ cp /proc/10099/fd/16 video.flv
Вот и всё. Это достаточно тривиальные вещи (многие, думаю, догадались, о чём пойдёт речь с одного лишь заголовка), но, надеюсь, для кого-то этот простой трюк окажется полезным.
UPD
kreon оформил эти действия в виде скрипта (я позволил себе немного модифицировать его, добавив аргумент):
#!/bin/sh
PID=`ps x | grep libflashplayer.so | grep -v grep | awk '{print $1}'`
FD=`lsof -p $PID | grep Flash | awk '{print $4}' | sed 's/u^//'`
cp /proc/$PID/fd/$FD "$1"
Использование:
$ saveflash.sh coolvideo.flv
UPD 2
В комментариях неоднократно указали на нерациональность такого способа и предложили взамен разнообразные программы и плагины браузеров для скачивания видео. Конечно, для скачивания с YouTube удобнее будет воспользоваться ими (хотя надо ещё посмотреть, не поломалась ли у них из-за вышеописанного возможность доставать видео из кеша браузера). Однако все эти плагины заточены на конкретный, пусть и достаточно большой, список видеохостингов. Данный же способ позволяет достать видео практически всегда, если видео загружается по HTTP, а не используется RTMP-стриминг.