Linux下使用lsof恢复误删文件

一、简述

刚刚在整理lsof命令用法的时候发现一个问题,就是在一个文件被一个进程占用的时候,当系统管理员由于误操作将该文件删除了,如果这期间占据该文件的进程并未重启的话,是可以通过该进程对应该文件的文件描述符将该文件恢复回来的。

因为在一个文件系统中,文件实际上是一个指向inode的链接, inode链接包含了文件的所有属性, 比如权限和所有者, 磁盘块地址(文件存储在磁盘的这些磁盘块中). 当你删除(rm)一个文件, 实际删除了指向inode的链接, 并没有删除inode的内容. 进程可能还在使用. 只有当inode的所有链接完全移去, 然后这些磁盘块将可以写入新的数据.
proc文件系统可以协助我们恢复数据. 每一个系统上的进程在/proc都有一个目录和自己的名字: 里面包含了一个fd(文件描述符)子目录(进程需要打开文件的所有链接). 如果从文件系统中删除一个文件, 此处还有一个inode的引用:
/proc/进程号/fd/文件描述符

二、实验演示

我们通过mysql客户端的输出写入到一个特定的日志文件中去,当mysql客户端不主动断开与服务端的连接时,对应该日志文件的文件描述符就不会被释放掉,文件被删除掉出后我们就可以通过该文件描述符去恢复该文件中的数据。

首先mysql的配置:

shell> cat /etc/my.cnf
[mysql]
tee=/tmp/mysql-query.log

这时候我们新开一个xshell会话窗口(WINDOW1),通过mysql在命令行建立一个mysql会话,注意这个窗口在后续的操作中不可断开,因为断开的结果就是这个进程结束,也就意味着会将其所占据的文件描述符全部释放掉,进而文件系统会将该inode节点置空,从而无法通过该方式读取到对应的数据快里面的数据。

WINDOW1> mysql -uroot -p
Logging to file '/tmp/mysql-query.log'
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 41
Server version: 5.6.29-76.2-log Percona Server (GPL), Release 76.2, Revision ddf26fe

Copyright (c) 2009-2016 Percona LLC and/or its affiliates
Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
| zabbix             |
+--------------------+
5 rows in set (0.01 sec)

于此同时我们新开一个xshell会话窗口(WINDOW2)去跟踪我们日志的输出信息(刚刚在WINDOW1的输出信息都会在该日志文件中记录出来):

WINDOW2> tail -F /tmp/mysql-query.log
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
| zabbix             |
+--------------------+
5 rows in set (0.01 sec)

然后我们新开一个xshell的会话窗口(WINDOW3)将该文件删除

WINDOW3> rm -f /tmp/mysql-query.log

这个时候在(WINDOW2)的窗口中会发现:
tail: `/tmp/mysql-query.log’ has become inaccessible: No such file or directory

我们后续在没有关闭掉(WINDOW1)中mysql时,无论我们在WINDOW1中通过mysql执行任何查询都不会在产生文件:/tmp/mysql-query.log,就算我们手动再创建与之同名的文件,也不会有日志写到该文件中来,这是因为在文件的删除、重建的过程当中该文件名所对应的inode节点已经发生变化,文件系统为新的inode节点分配的磁盘块没有包含之前的数据;而且由于WINDOW1中的mysql进程中的文件描述符所对应的inode节点为旧的inode节点,该进程所产生的日志信息依然后源源不断的写入到旧的inode节点所对应的磁盘块中去,而我们之所以无法通过文件名查看,是因为文件系统中已经没有一个文件名的链接指向该inode节点了。

### WINDOW1
mysql> use mysql;
use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+---------------------------+
| Tables_in_mysql           |
+---------------------------+
| columns_priv              |
| db                        |
| event                     |
| func                      |
| general_log               |
| help_category             |
| help_keyword              |
| help_relation             |
| help_topic                |
| innodb_index_stats        |
| innodb_table_stats        |
| ndb_binlog_index          |
| plugin                    |
| proc                      |
| procs_priv                |
| proxies_priv              |
| servers                   |
| slave_master_info         |
| slave_relay_log_info      |
| slave_worker_info         |
| slow_log                  |
| tables_priv               |
| time_zone                 |
| time_zone_leap_second     |
| time_zone_name            |
| time_zone_transition      |
| time_zone_transition_type |
| user                      |
+---------------------------+
28 rows in set (0.01 sec)
WINDOW3>  cat /tmp/mysql-query.log
cat: /tmp/mysql-query.log: No such file or directory

这个时候我们就去查找WINDOW1中mysql客户端链接mysqld的进程ID(PID),然后通过proc文件系统中的文件描述符恢复数据:

WINDOW3> lsof | grep '/tmp/mysql-query.log'
mysql 8548 root 3w REG 8,5 6823 653125 /tmp/mysql-query.log (deleted)
WINDOW3> ls -l /proc/8548/fd/ | grep "mysql-query.log"
l-wx------ 1 root root 64 Apr 21 04:31 3 -> /tmp/mysql-query.log (deleted)
WINDOW3> cat /proc/8548/fd/3 > /tmp/restore.log

我们会看到在/tmp/restore.log 文件中包含了我们所有的查询记录,包括在我们删除掉/tmp/mysql-query.log 文件之后执行了一些查询日志,也记录了下来。

然后我们将/tmp/restore.log的数据补充到/tmp/mysql-query.log ,然后在WINDOWS1中关闭mysql的连接,然后重新建立,后面的日志就依然后重新写入到/tmp/mysql-query.log文件中来了。

暂无评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Time limit is exhausted. Please reload CAPTCHA.