Certa vez, em uma entrevista, me perguntaram o que eu faria se descobrisse um serviço não funcionando porque o disco estava cheio.
Claro que eu respondi que veria o que estava ocupando esse espaço e, se possível, limparei o lugar.
Então o entrevistador perguntou, e se a partição não tiver espaço livre, mas você também não vê arquivos que ocupem todo o espaço?
A isso eu disse que sempre se pode verificar os descritores de arquivo abertos, por exemplo, usando o comando lsof e entender qual aplicativo ocupou todo o espaço disponível, e então podemos agir conforme as circunstâncias, dependendo se os dados são necessários.
O entrevistador me interrompeu na última palavra, completando sua pergunta: "Suponha que os dados não sejam necessários, é apenas um log de depuração, mas o aplicativo não funciona porque não consegue gravar o log de depuração"?
"Ok", eu respondi, "podemos desabilitar a depuração na configuração do aplicativo e reiniciá-lo."
O entrevistador retrucou: "Não, não podemos reiniciar o aplicativo, temos dados importantes ainda armazenados na memória, e clientes importantes estão conectados ao serviço, que não podemos fazer desconectar-se novamente."
"Bem, então", eu disse, "se não podemos reiniciar o aplicativo e os dados não são importantes, podemos simplesmente limpar aquele arquivo aberto através do descritor de arquivo, mesmo que não o vejamos no comando ls no sistema de arquivos."
O entrevistador ficou satisfeito, mas eu não.
Então eu pensei, por que a pessoa que está avaliando meu conhecimento não investiga mais a fundo? E se os dados forem realmente importantes? E se não pudermos reiniciar o processo, e esse processo estiver escrevendo no sistema de arquivos na partição que não tem espaço livre? E se não pudermos perder não apenas os dados já gravados, mas também os dados que esse processo está escrevendo ou tentando gravar?
Tuzik
No início da minha carreira, tentei criar um pequeno aplicativo para armazenar informações sobre usuários. Naquela época, eu pensava em como associar um usuário aos seus dados. Por exemplo, tenho Ivanov Ivan Ivanovich e ele possui algumas informações, mas como vinculá-las? Posso indicar diretamente que o cachorro chamado "Tuzik" pertence a este Ivan. Mas e se ele mudar de nome e passar a ser, por exemplo, Olya? Nesse caso, nossa Olya Ivanovna Ivanova não terá mais um cachorro, enquanto nosso Tuzik ainda pertencerá a um Ivan que não existe mais. A solução para esse problema veio na forma de um banco de dados que concedeu a cada usuário um identificador único (ID), e meu Tuzik foi vinculado a esse ID, que, na verdade, era apenas um número sequencial. Assim, o dono do Tuzik tinha um ID 2, e em determinado momento esse ID pertencia a Ivan, e depois a Olya. O problema da humanidade e da pecuária estava praticamente resolvido.
Descrição do arquivo
O problema entre o arquivo e o programa que trabalha com ele é semelhante ao de nosso cachorro e seu dono. Suponha que eu abri um arquivo chamado ivan.txt e comecei a escrever a palavra tuzik, mas consegui gravar apenas a primeira letra "t" no arquivo, e esse arquivo foi renomeado por alguém, por exemplo, para olya.txt. Mas o arquivo continua sendo o mesmo, e eu ainda quero gravar meu Tuzik nele. Cada vez que abro o arquivo através de uma chamada de sistema em qualquer linguagem de programação, eu obtenho um ID único que me aponta para o arquivo, esse ID é o descritor do arquivo. E não importa o que fizerem com esse arquivo depois, ele pode ser excluído, renomeado, seu proprietário pode ser alterado ou seus direitos de leitura e gravação podem ser revogados, ainda assim terei acesso a ele, porque no momento da abertura do arquivo eu tinha direitos para lê-lo e/ou escrevê-lo e consegui começar a trabalhar com ele, e portanto devo continuar fazendo isso.
No Linux, a biblioteca libc abre 3 descritores de arquivo para cada aplicativo (processo) em execução, com números 0, 1, 2. Você pode encontrar mais informações nos links e
- O descritor de arquivo 0 é chamado de STDIN e está associado à entrada de dados do aplicativo.
- O descritor de arquivo 1 é chamado STDOUT e é usado por aplicativos para saída de dados, como os comandos print.
- O descritor de arquivo 2 é chamado STDERR e é usado por aplicativos para saída de dados relacionados a erros.
Se no seu programa você abrir algum arquivo para leitura ou gravação, provavelmente receberá o primeiro ID livre, que será o número 3.
A lista de descritores de arquivo pode ser vista em qualquer processo se você souber seu PID.
Por exemplo, vamos abrir o console com bash e verificar o PID do nosso processo.
[user@localhost ]$ echo $$
15771
No segundo console, vamos iniciar
[user@localhost ]$ ls -lah /proc/15771/fd/
total 0
dr-x------ 2 user user 0 Out 7 15:42 .
dr-xr-xr-x 9 user user 0 Out 7 15:42 ..
lrwx------ 1 user user 64 Out 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Out 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Out 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Out 7 15:42 255 -> /dev/pts/21
Você pode ignorar o descritor de arquivo número 255 para os fins deste artigo, ele foi aberto para suas próprias necessidades pelo próprio bash e não pela biblioteca vinculada.
Agora, todos os 3 descritores de arquivo estão ligados ao dispositivo de terminal pseudo. , mas ainda podemos manipulá-los, por exemplo, vamos executar no segundo console
[user@localhost ]$ echo "hello world" > /proc/15771/fd/0
E no primeiro console veremos
[user@localhost ]$ hello world
Redirect e Pipe
Você pode facilmente redefinir esses 3 descritores de arquivo em qualquer processo, inclusive no bash, por exemplo, através de um pipe, conectando dois processos, veja
[user@localhost ]$ cat /dev/zero | sleep 10000
Você pode executar este comando com strace -f e ver o que acontece por dentro, mas eu vou explicar brevemente.
Nosso processo pai bash com PID 15771 analisa nosso comando e entende quantos comandos exatamente queremos executar, no nosso caso são dois: cat e sleep. O bash sabe que precisa criar dois processos filhos e conectá-los com um pipe. Portanto, o bash precisará de 2 processos filhos e um pipe.
Antes de criar os processos filhos, o bash faz uma chamada de sistema e obtém novos descritores de arquivo para o buffer temporário do pipe, mas esse buffer ainda não conecta nossos dois processos filhos.
Para o processo pai, parece que o pipe já existe, mas os processos filhos ainda não:
PID command
15771 bash
lrwx------ 1 user user 64 Out 7 15:42 0 -> /dev/pts/21
lrwx------ 1 user user 64 Out 7 15:42 1 -> /dev/pts/21
lrwx------ 1 user user 64 Out 7 15:42 2 -> /dev/pts/21
lrwx------ 1 user user 64 Out 7 15:42 3 -> pipe:[253543032]
lrwx------ 1 user user 64 Out 7 15:42 4 -> pipe:[253543032]
lrwx------ 1 user user 64 Out 7 15:42 255 -> /dev/pts/21
Então, com o chamado do sistema o bash cria dois processos filhos, e nossos três processos ficarão assim:
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
Não devemos esquecer que o clone clona o processo junto com todos os descritores de arquivo, portanto, no processo pai e nos filhos, eles serão idênticos. A tarefa do processo pai com PID 15771 é monitorar os processos filhos, então ele apenas aguarda uma resposta dos filhos.
Portanto, ele não precisa de um pipe, e fecha os descritores de arquivo com os números 3 e 4.
No primeiro processo filho bash com PID 9004, o chamado do sistema , muda nosso descritor de arquivo STDOUT com o número 1 para um descritor de arquivo apontando para o pipe, que neste caso é o número 3. Assim, tudo o que o primeiro processo filho com PID 9004 escrever no STDOUT irá automaticamente para o buffer do pipe.
No segundo processo filho com PID 9005, o bash troca, usando dup2, o descritor de arquivo STDIN com o número 0. Agora, tudo o que nosso segundo bash com PID 9005 ler virá do pipe.
Depois disso, nos processos filhos, os descritores de arquivo com os números 3 e 4 também são fechados, pois não são mais utilizados.
O descritor de arquivo 255 eu ignoro intencionalmente, ele é usado para as necessidades internas do próprio bash e será fechado nos processos filhos também.
Em seguida, no primeiro processo filho com PID 9004, o bash inicia, usando o chamado do sistema o arquivo executável que especificamos na linha de comando, que neste caso é /usr/bin/cat.
No segundo processo filho com PID 9005, o bash inicia o segundo arquivo executável que especificamos, que neste caso é /usr/bin/sleep.
A chamada de sistema exec não fecha os descritores de arquivo se não foram abertos com a flag O_CLOEXEC durante a execução da chamada open. No nosso caso, após a execução dos arquivos executáveis, todos os descritores de arquivo atuais serão mantidos.
Conferimos no console:
[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
Como você pode ver, o número exclusivo do nosso pipe coincide em ambos os processos. Assim, temos uma conexão entre dois processos diferentes com um único pai.
Para quem não está familiarizado com as chamadas de sistema usadas pelo bash, recomendo fortemente executar os comandos através do strace e observar o que acontece internamente, por exemplo, assim:
strace -s 1024 -f bash -c "ls | grep hello"
Voltando ao nosso problema de falta de espaço em disco e a tentativa de salvar dados sem reiniciar o processo. Vamos escrever um pequeno programa que grava no disco cerca de 1 megabyte por segundo. Se, por algum motivo, não conseguirmos gravar os dados no disco, simplesmente iremos ignorar isso e tentar gravar os dados novamente após um segundo. No exemplo, estou usando Python, mas você pode usar qualquer outra linguagem de programação.
[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
Vamos executar o programa e observar os descritores de arquivo
[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 7 Out 16:29 .
dr-xr-xr-x 9 user user 0 7 Out 16:29 ..
lrwx------ 1 user user 64 7 Out 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 7 Out 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 7 Out 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 7 Out 16:29 3 -> /home/user/123.txt
Como podemos ver, temos nossos 3 descritores de arquivo padrão e mais um que abrimos. Vamos verificar o tamanho do arquivo:
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 117M 7 Out 16:30 123.txt
Os dados estão sendo escritos, vamos tentar mudar as permissões do arquivo:
[user@localhost ]$ sudo chown root: 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 168M 7 Out 16:31 123.txt
[user@localhost ]$ ls -lah 123.txt
-rw-rw-r-- 1 root root 172M 7 Out 16:31 123.txt
Podemos ver que os dados ainda estão sendo escritos, embora nosso usuário não tenha permissão para escrever no arquivo. Vamos tentar deletá-lo:
[user@localhost ]$ sudo rm 123.txt
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory
Para onde os dados estão sendo escritos? E realmente estão sendo escritos? Vamos verificar:
[user@localhost ]$ ls -la /proc/3762/fd
total 0
dr-x------ 2 user user 0 7 Out 16:29 .
dr-xr-xr-x 9 user user 0 7 Out 16:29 ..
lrwx------ 1 user user 64 7 Out 16:29 0 -> /dev/pts/22
lrwx------ 1 user user 64 7 Out 16:29 1 -> /dev/pts/22
lrwx------ 1 user user 64 7 Out 16:29 2 -> /dev/pts/22
l-wx------ 1 user user 64 7 Out 16:29 3 -> /home/user/123.txt (deleted)
Sim, nosso descritor de arquivo ainda existe, e podemos trabalhar com esse descritor de arquivo como se fosse nosso antigo arquivo, podemos lê-lo, limpá-lo e copiá-lo.
Vamos verificar o tamanho do arquivo:
[user@localhost ]$ lsof | grep 123.txt
python 31083 user 3w REG 8,5 19923457 2621522 /home/user/123.txt
O tamanho do arquivo é 19923457. Vamos tentar limpar o arquivo:
[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
Como podemos ver, o tamanho do arquivo só aumenta e nosso truncate não funcionou. Vamos consultar a documentação da chamada de sistema. Se ao abrir o arquivo utilizarmos a flag O_APPEND, então a cada gravação, o sistema operacional verifica o tamanho do arquivo e escreve os dados no final do arquivo, fazendo isso de forma atômica. Isso permite que várias threads ou processos escrevam no mesmo arquivo. Mas no nosso código, não utilizamos essa flag. Podemos ver um tamanho diferente do arquivo no lsof após o truncate apenas se abrirmos o arquivo para escrita incremental, portanto no nosso código, em vez de
with open("123.txt", "w") as f:
devemos usar
with open("123.txt", "a") as f:
Vamos verificar com a flag «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
e com a flag «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
Programando um processo já em execução
Frequentemente, os programadores usam depuradores (como GDB) ou diferentes níveis de logging no aplicativo ao criar e testar um programa. O Linux oferece a capacidade de modificar e escrever em um programa já em execução, como alterar valores de variáveis, definir breakpoints, etc.
Voltando à questão original sobre a falta de espaço em disco para gravar o arquivo, vamos tentar simular o problema.
Vamos criar um arquivo para nossa seção, que será montado como um disco separado:
[user@localhost ~]$ dd if=/dev/zero of=~/tempfile_for_article.dd bs=1M count=10
10+0 registros de entrada
10+0 registros de saída
10485760 bytes (10 MB) copiados, 0.00525929 s, 2.0 GB/s
[user@localhost ~]$
Vamos criar um sistema de arquivos:
[user@localhost ~]$ mkfs.ext4 ~/tempfile_for_article.dd
mke2fs 1.42.9 (28-Dec-2013)
/home/user/tempfile_for_article.dd não é um dispositivo especial de bloco.
Proseguir mesmo assim? (y,n) y
...
Escrevendo superblocos e informações de contabilidade do sistema de arquivos: feito
[user@localhost ~]$
Montando o sistema de arquivos:
[user@localhost ~]$ sudo mount ~/tempfile_for_article.dd /mnt/
[sudo] senha para user:
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 172K 7.9M 3% /mnt
Criando um diretório com nosso proprietário:
[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs
Abrindo o arquivo apenas para escrita em nosso programa:
with open("/mnt/logs/123.txt", "w") as f:
Iniciamos
[user@localhost ]$ python openforwrite.py
Aguardando alguns segundos
[user@localhost ~]$ df -h | grep mnt
/dev/loop0 8.7M 8.0M 0 100% /mnt
Assim, encontramos o problema descrito no início deste artigo. Espaço livre 0, ocupado 100%.
Lembramos que, conforme as condições do problema, estamos tentando gravar dados muito importantes que não podem ser perdidos. E precisamos corrigir o serviço sem reiniciar o processo.
Suponha que ainda temos espaço em disco, mas em outra partição, por exemplo, em /home.
Vamos tentar "reprogramar em tempo real" nosso código.
Observando o PID do nosso processo que consumiu todo o espaço em disco:
[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
Conectando-se ao processo via gdb
[user@localhost ~]$ gdb -p 10078
...
(gdb)
Observando os descritores de arquivo abertos:
(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
Vendo informações sobre o descritor de arquivo número 3, que nos interessa
(gdb) shell cat /proc/10078/fdinfo/3
pos: 8189952
flags: 0100001
mnt_id: 482
Lembrando qual chamada de sistema o Python faz (veja acima, onde executamos strace e encontramos a chamada open), ao processar nosso código para abrir um arquivo, fazemos o mesmo por conta do nosso processo, mas precisamos substituir os bits O_WRONLY|O_CREAT|O_TRUNC por um valor numérico. Para isso, abrimos as fontes do núcleo, por exemplo, e verificamos quais flags correspondem a cada um.
#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000
Unimos todos os valores em um só, obtendo 00001101.
Executamos nossa chamada a partir do gdb.
(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4
Assim, obtivemos um novo descritor de arquivo com o número 4 e um novo arquivo aberto em outra partição, vamos verificar:
(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
Lembramos do exemplo com pipe – como o bash muda os descritores de arquivo, e já aprendemos a chamada de sistema dup2.
Vamos tentar substituir um descritor de arquivo por outro.
(gdb) call dup2(4,3)
$2 = 3
Verificamos:
(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
Fechamos o descritor de arquivo 4, pois não precisamos dele:
(gdb) call close (4)
$1 = 0
E saímos do 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
Verificamos o novo arquivo:
[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
Como podemos ver, os dados estão sendo escritos no novo arquivo, vamos verificar o antigo:
[user@localhost ~]$ ls -lah /mnt/logs/123.txt
-rw-rw-r-- 1 user user 7.9M Oct 8 11:08 /mnt/logs/123.txt
Os dados não foram perdidos, o aplicativo está funcionando, e os logs estão sendo escritos em um novo local.
Vamos complicar um pouco a tarefa.
Suponha que os dados sejam importantes, mas não temos espaço em disco em nenhuma das partições e não podemos conectar um disco.
O que podemos fazer é redirecionar nossos dados para algum lugar, por exemplo, para um pipe, e os dados do pipe, por sua vez, redirecionar para a rede através de algum programa, como o netcat.
Podemos criar um pipe nomeado com o comando mkfifo. Ele criará um arquivo pseudofísico no sistema de arquivos, mesmo que não haja espaço livre nele.
Reiniciamos o aplicativo e verificamos:
[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
Não há espaço em disco, mas conseguimos criar um pipe nomeado lá:
[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
Agora precisamos de alguma forma para enviar todos os dados que entram neste pipe para outro servidor pela rede, e o netcat servirá para isso.
No servidor remote-server.example.com, iniciamos
[user@localhost ~]$ nc -l 7777 > 123.txt
No nosso servidor problemático, iniciamos em um terminal separado
[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe
Agora todos os dados que entrarem no pipe automaticamente irão para o stdin do netcat, que os enviará pela rede para a porta 7777.
Tudo o que precisamos fazer é começar a escrever nossos dados neste pipe nomeado.
Já temos um aplicativo em execução:
[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
De todos os flags, precisamos apenas de O_WRONLY, pois o arquivo já existe e não precisamos limpá-lo.
[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
Verificando o servidor remoto remote-server.example.com
[user@localhost ~]$ ls -lah 123.txt
-rw-rw-r-- 1 user user 38M Oct 8 14:21 123.txt
Os dados estão fluindo, verificando o servidor problemático
[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
Os dados foram salvos, o problema está resolvido.
Aproveitando a oportunidade, mando um abraço para os colegas da empresa Degiro.
Ouçam os podcasts da Rádio-T.
Fiquem bem.
Como tarefa de casa, sugiro pensar sobre o que haverá nos descritores de arquivo do processo cat e sleep se executar o seguinte comando:
[user@localhost ~]$ cat /dev/zero 2>/dev/null| sleep 10000
Fonte: habr.com
