Descritor de arquivo no Linux com exemplos

Certa vez, durante uma entrevista, me perguntaram: o que você fará se descobrir que um serviço não está funcionando devido ao fato de o disco estar sem espaço?

Claro, respondi que veria o que estava ocupado neste local e, se possível, limparia o local.
Aí o entrevistador perguntou: e se não houver espaço livre na partição, mas você também não vir nenhum arquivo que ocuparia todo o espaço?

Para isso eu disse que você sempre pode olhar os descritores de arquivos abertos, por exemplo com o comando lsof, e entender qual aplicativo ocupou todo o espaço disponível, e então você pode agir de acordo com as circunstâncias, dependendo se os dados são necessários .

O entrevistador me interrompeu na última palavra, acrescentando à sua pergunta: “Suponha que não precisamos dos dados, é apenas um log de depuração, mas o aplicativo não funciona porque não consegue escrever um debug”?

“Tudo bem”, respondi, “podemos desligar a depuração na configuração do aplicativo e reiniciá-lo”.
O entrevistador objetou: “Não, não podemos reiniciar o aplicativo, ainda temos dados importantes armazenados na memória e clientes importantes estão conectados ao próprio serviço, que não podemos forçar a reconectar”.

“ok”, eu disse, “se não pudermos reiniciar o aplicativo e os dados não forem importantes para nós, podemos simplesmente limpar esse arquivo aberto por meio 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 pensei: por que a pessoa que testa meu conhecimento não se aprofunda? Mas e se os dados forem importantes, afinal? E se não conseguirmos reiniciar um processo e o processo gravar no sistema de arquivos em uma partição que não possui espaço livre? E se não pudermos perder não só os dados que já foram escritos, mas também os dados que este processo escreve ou tenta escrever?

Grande chapéu

No início da minha carreira, tentei criar um pequeno aplicativo que precisava armazenar informações do usuário. E então pensei: como posso combinar o usuário com seus dados? Por exemplo, eu tenho Ivanov Ivan Ivanovich e ele tem algumas informações, mas como posso fazer amizade com eles? Posso apontar diretamente que o cachorro chamado “Tuzik” pertence a esse mesmo Ivan. Mas e se ele mudar de nome e em vez de Ivan se tornar, por exemplo, Olya? Então acontecerá que nossa Olya Ivanovna Ivanova não terá mais cachorro e nosso Tuzik ainda pertencerá ao inexistente Ivan. Um banco de dados que dava a cada usuário um identificador único (ID) ajudou a resolver esse problema, e meu Tuzik estava vinculado a esse ID, que, na verdade, era apenas um número de série. Assim, o dono do ás tinha o número de identificação 2, e em algum momento Ivan estava com esse ID, e então Olya passou a ter o mesmo ID. O problema da humanidade e da pecuária estava praticamente resolvido.

Descritor de arquivo

O problema do arquivo e do programa que funciona com esse arquivo é aproximadamente o mesmo do nosso cachorro e do nosso homem. Suponha que eu abri um arquivo chamado ivan.txt e comecei a escrever a palavra tuzik nele, mas só consegui escrever a primeira letra “t” no arquivo, e esse arquivo foi renomeado por alguém, por exemplo, para olya.txt. Mas o arquivo continua o mesmo e ainda quero gravar meu ás nele. Cada vez que um arquivo é aberto por uma chamada do sistema aberto em qualquer linguagem de programação recebo um ID único que me aponta para um arquivo, esse ID é o descritor do arquivo. E não importa o que e quem fará a seguir com este arquivo, ele pode ser excluído, pode ser renomeado, o proprietário pode ser alterado ou os direitos de leitura e gravação podem ser retirados, ainda terei acesso a ele, porque no momento de abrir o arquivo eu tinha o direito de lê-lo e/ou escrevê-lo e consegui começar a trabalhar com ele, o que significa que devo continuar a fazê-lo.

No Linux, a biblioteca libc abre 3 arquivos descritores para cada aplicativo (processo) em execução, numerados 0,1,2. Mais informações podem ser encontradas nos links homem estúdio и homem stdout

  • O descritor de arquivo 0 é chamado STDIN e está associado à entrada do aplicativo
  • O descritor de arquivo 1 é chamado STDOUT e é usado por aplicativos para gerar dados, como comandos de impressão
  • O descritor de arquivo 2 é chamado STDERR e é usado por aplicativos para gerar mensagens de erro.

Se em seu programa você abrir qualquer arquivo para leitura ou gravação, provavelmente receberá o primeiro ID gratuito e será o número 3.

A lista de descritores de arquivos pode ser visualizada para qualquer processo se você souber seu PID.

Por exemplo, vamos abrir o console bash e ver o PID do nosso processo

[user@localhost ]$ echo $$
15771

No segundo console vamos rodar

[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

Você pode ignorar com segurança o descritor de arquivo número 255 para os propósitos deste artigo; ele foi aberto para suas necessidades pelo próprio bash, e não pela biblioteca vinculada.

Agora todos os 3 arquivos descritores estão associados ao dispositivo pseudo terminal / dev / pts, mas ainda podemos manipulá-los, por exemplo, executá-los em um segundo console

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

E no primeiro console veremos

[user@localhost ]$ hello world

Redirecionar e canalizar

Você pode substituir facilmente esses 3 arquivos descritores em qualquer processo, inclusive no bash, por exemplo, por meio de um canal conectando dois processos, consulte

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

Você mesmo pode executar este comando com linha -f e veja o que está acontecendo lá dentro, mas vou lhe contar brevemente.

Nosso processo bash pai com PID 15771 analisa nosso comando e entende exatamente quantos comandos queremos executar, no nosso caso são dois: cat e sleep. Bash sabe que precisa criar dois processos filhos e mesclá-los em um pipe. No total, o bash precisará de 2 processos filhos e um pipe.

Bash executa uma chamada de sistema antes de criar processos filhos tubo e recebe novos descritores de arquivo no buffer de pipe temporário, mas esse buffer ainda não conecta nossos dois processos filhos.

Para o processo pai, parece que já existe um canal, mas ainda não há processos filhos:

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

Então usando a chamada do sistema clonar 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 esqueça que clone clona o processo junto com todos os descritores de arquivo, portanto eles serão iguais no processo pai e nos filhos. A tarefa do processo pai com PID 15771 é monitorar os processos filhos, portanto, ele simplesmente aguarda uma resposta dos filhos.

Portanto, não precisa de pipe e fecha os descritores de arquivos 3 e 4.

No primeiro processo bash filho com PID 9004, a chamada do sistema dup2, altera nosso descritor de arquivo STDOUT número 1 para um descritor de arquivo apontando para pipe, no nosso caso é o número 3. Assim, tudo o que o primeiro processo filho com PID 9004 escreve em STDOUT irá automaticamente terminar no buffer de pipe.

No segundo processo filho com PID 9005, o bash usa dup2 para alterar o descritor de arquivo STDIN número 0. Agora, tudo o que nosso segundo bash com PID 9005 ler será lido no pipe.

Depois disso, os descritores de arquivos 3 e 4 também são fechados nos processos filhos, pois não são mais utilizados.

Ignoro deliberadamente o descritor de arquivo 255; ele é usado para fins internos pelo próprio bash e também será fechado em processos filhos.

A seguir, no primeiro processo filho com PID 9004, o bash começa a usar uma chamada de sistema exec o arquivo executável que especificamos na linha de comando, no nosso caso é /usr/bin/cat.

No segundo processo filho com PID 9005, o bash executa o segundo executável que especificamos, no nosso caso /usr/bin/sleep.

A chamada do sistema exec não fecha identificadores de arquivo, a menos que eles tenham sido abertos com o sinalizador O_CLOEXEC no momento em que a chamada aberta foi feita. No nosso caso, após iniciar os arquivos executáveis, todos os descritores de arquivos atuais serão salvos.

Verifique 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 único do nosso pipe é o mesmo em ambos os processos. Assim temos uma conexão entre dois processos diferentes com o mesmo pai.

Para quem não está familiarizado com as chamadas de sistema que o bash usa, recomendo fortemente executar os comandos através do strace e ver o que está acontecendo internamente, por exemplo assim:

strace -s 1024 -f bash -c "ls | grep hello"

Voltemos ao nosso problema de ficar sem espaço em disco e tentar salvar os dados sem reiniciar o processo. Vamos escrever um pequeno programa que gravará aproximadamente 1 megabyte por segundo no disco. Além disso, se por algum motivo não conseguirmos gravar dados no disco, simplesmente ignoraremos isso e tentaremos gravar os dados novamente em um segundo. No exemplo que estou usando Python, 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 ver 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 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

Como você pode 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 Oct  7 16:30 123.txt

Os dados estão sendo gravados, tentamos alterar 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 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

Vemos que os dados ainda estão sendo gravados, embora nosso usuário não tenha permissão para gravar no arquivo. Vamos tentar removê-lo:

[user@localhost ]$ sudo rm 123.txt 
[user@localhost ]$ ls 123.txt
ls: cannot access 123.txt: No such file or directory

Onde os dados são gravados? E eles estão escritos? Nós verificamos:

[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)

Sim, nosso descritor de arquivo ainda existe e podemos tratá-lo como nosso arquivo antigo, podemos lê-lo, limpá-lo e copiá-lo.

Vejamos 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 você pode ver, o tamanho do arquivo só está aumentando e nosso tronco não funcionou. Vejamos a documentação da chamada do sistema aberto. Se usarmos o sinalizador O_APPEND ao abrir um arquivo, a cada gravação, o sistema operacional verifica o tamanho do arquivo e grava os dados no final do arquivo, e faz isso atomicamente. Isso permite que vários threads ou processos gravem no mesmo arquivo. Mas em nosso código não usamos esse sinalizador. Podemos ver um tamanho de arquivo diferente em lsof após trunk somente se abrirmos o arquivo para gravação adicional, o que significa em nosso código

with open("123.txt", "w") as f:

temos que colocar

with open("123.txt", "a") as f:

Verificando com o sinalizador “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 bandeira "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

Freqüentemente, os programadores, ao criar e testar programas, usam depuradores (por exemplo, GDB) ou vários níveis de registro no aplicativo. O Linux oferece a capacidade de realmente escrever e alterar um programa já em execução, por exemplo, alterar os valores das variáveis, definir um ponto de interrupção, etc., etc.

Voltando à questão original sobre espaço em disco insuficiente para gravar um arquivo, vamos tentar simular o problema.

Vamos criar um arquivo para nossa partição, que montaremos como um disco separado:

[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 ~]$

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 is not a block special device.
Proceed anyway? (y,n) y
...
Writing superblocks and filesystem accounting information: done
[user@localhost ~]$

Monte o sistema de arquivos:

[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

Criamos um diretório com nosso proprietário:

[user@localhost ~]$ sudo mkdir /mnt/logs
[user@localhost ~]$ sudo chown user: /mnt/logs

Vamos abrir o arquivo para gravação apenas em nosso programa:

with open("/mnt/logs/123.txt", "w") as f:

Lançamos

[user@localhost ]$ python openforwrite.py 

Esperamos alguns segundos

[user@localhost ~]$ df -h | grep mnt
/dev/loop0      8.7M  8.0M     0 100% /mnt

Portanto, temos o problema descrito no início deste artigo. Espaço livre 0, 100% ocupado.

Lembramos que de acordo com as condições da tarefa, estamos tentando registrar dados muito importantes que não podem ser perdidos. E ao mesmo tempo, precisamos consertar o serviço sem reiniciar o processo.

Digamos que ainda temos espaço em disco, mas em uma partição diferente, por exemplo, em/home.

Vamos tentar “reprogramar dinamicamente” nosso código.

Vejamos 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

Conecte-se ao processo via gdb

[user@localhost ~]$ gdb -p 10078
...
(gdb) 

Vejamos os descritores de arquivos 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

Observamos as 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

Tendo em mente 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 em nome de nosso processo, mas precisamos do método O_WRONLY|O_CREAT| Os bits O_TRUNC são substituídos por um valor numérico. Para fazer isso, abra os fontes do kernel, por exemplo aqui e veja quais sinalizadores são responsáveis ​​pelo que

#define O_WRONLY 00000001
#define O_CREAT 00000100
#define O_TRUNC 00001000

Combinamos todos os valores em um, obtemos 00001101

Executamos nossa chamada do gdb

(gdb) call open("/home/user/123.txt", 00001101,0666)
$1 = 4

Então obtivemos um novo descritor de arquivo com o número 4 e um novo arquivo aberto em outra partição, 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 -> /mnt/logs/123.txt
l-wx------ 1 user user 64 Oct  8 11:15 4 -> /home/user/123.txt

Lembramos o exemplo com pipe - como o bash altera os descritores de arquivo, e já aprendemos a chamada do sistema dup2.

Tentamos 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 saia 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

Verificando 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 você pode ver, os dados são gravados em um 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

Nenhum dado é perdido, o aplicativo funciona, os logs são gravados em um novo local.

Vamos complicar um pouco a tarefa

Vamos imaginar que os dados são importantes para nós, mas não temos espaço em disco em nenhuma das partições e não podemos conectar o disco.

O que podemos fazer é redirecionar nossos dados para algum lugar, por exemplo, para um pipe, e por sua vez redirecionar os dados do pipe para a rede por meio de algum programa, por exemplo, netcat.
Podemos criar um pipe nomeado com o comando mkfifo. Ele criará um pseudoarquivo no sistema de arquivos, mesmo que não haja espaço livre nele.

Reinicie o aplicativo e verifique:

[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 criamos com sucesso um canal 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 agrupar todos os dados que vão para este canal para outro servidor através da rede; o mesmo netcat é adequado para isso.

No servidor remote-server.example.com executamos

[user@localhost ~]$ nc -l 7777 > 123.txt 

Em nosso servidor problemático, lançamos em um terminal separado

[user@localhost ~]$ nc remote-server.example.com 7777 < /mnt/logs/megapipe 

Agora todos os dados que vão parar no pipe irão automaticamente para o stdin do netcat, que os enviará para a rede na porta 7777.

Tudo o que precisamos fazer é começar a escrever nossos dados neste pipe nomeado.

Já temos o 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, só precisamos 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 chegando, verificamos o servidor com problema

[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 são salvos, o problema está resolvido.

Aproveito para cumprimentar meus colegas de Degiro.
Ouça podcasts da Rádio-T.

Bom para todos.

Como lição de casa, sugiro que você pense no que estará no processo dos descritores de arquivo cat e sleep se você executar o seguinte comando:

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

Fonte: habr.com

Adicionar um comentário