Pull to refresh

Сохраняем видео из Flash Player 10.2 или unlink нам не помеха

Configuring Linux *
Я, как, вероятно, и многие другие пользователи 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-стриминг.
Tags:
Hubs:
Total votes 135: ↑130 and ↓5 +125
Views 39K
Comments Comments 53