先明确需要:用C
语言实现判断文件是文本文件还是二进制文件,或者其余压缩格式文件。
文件的类型
Linux
零碎下,万物皆文件。
为了将所有的货色都能当成文件来治理,Linux
零碎将文件分成了七种类型,别离如下:
类型 | 简写 | S_IFMT | st_mode | 阐明 |
---|---|---|---|---|
块设施 | b | S_IFBLK | S_ISBLK(m) | 零碎存取数据的接口设施,例如硬盘 |
字符设施 | c | S_IFCHR | S_ISCHR(m) | 串行端口的接口设施,例如键盘、鼠标、打印机、tty 终端 |
目录 | d | S_IFDIR | S_ISDIR(m) | 文件夹 |
链接文件 | l | S_IFLNK | S_ISLNK(m) | 符号链接,分软链接和硬链接 |
套接字 | s | S_IFSOCK | S_ISSOCK(m) | 用于网络通信 |
一般文件 | - | S_IFREG | S_ISREG(m) | 分纯文本文件和二进制文件 |
命名管道 | p | S_IFIFO | S_ISFIFO(m) | 命名管道文件 |
上表中第三、第四列是Linux
下应用stat
函数判断文件类型提供的一些宏定义。如判断一个文件是否属于一般文件,能够应用上面的代码:
stat(pathname, &sb);if ((sb.st_mode & S_IFMT) == S_IFREG) { /* Handle regular file */}
或者间接应用:
stat(pathname, &sb);if (S_ISREG(sb.st_mode)) { /* Handle regular file */}
然而咱们的需要是判断文件是否属于文本文件还是二进制文件。而这两种都属于S_IFREG
一般文件,因而无奈应用下面的办法进行判断。
万能的file命令
file
命令是Linux
下用来检测文件类型的一个内置的命令。
大略原理就是读取一个文件的后面1024
个字节,而后依据magic
(/etc/magic
或者 /usr/share/misc/magic
) 里对应的规定剖析出文件头,并打印到屏幕上。
应用也很简略,间接file
前面跟上文件名即可:
[root@ck08 ~]# file anaconda-ks.cfganaconda-ks.cfg: ASCII text[root@ck08 ~]# file tls.pcaptls.pcap: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)[root@ck08 ~]# file zlib-1.2.11.tar.gzzlib-1.2.11.tar.gz: gzip compressed data, was "zlib-1.2.11.tar", from Unix, last modified: Mon Jan 16 01:36:58 2017, max compression[root@ck08 ~]# file /usr/bin/grep/usr/bin/grep: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=bb5d89868c5a04ae48f76250559cb01fae1cd762, stripped
从下面的示例中,能够看出file
命令很弱小,简直能够辨认出文件的具体类型,甚至是编码,压缩格局,大小端等具体的信息。
因而,这个命名是合乎咱们的需要的。
然而,咱们须要的是C
语言实现,因而,不得不钻研magic
的文件头规定。
magic文件规定
文件中的每行都指定了一个规定测试去测验文件类型,这个规定由4
个域指定。别离为offset
、type
、test
、message
。
offset
- 指定由文件起始的第几个
byte
开始测验。
- 指定由文件起始的第几个
type
要进行测验的数据类型,即由
offset
那个byte
开始的那个数据类型是什么。具体有哪些数据类型,能够参考magic(5)
。罕用的数据类型有byte
:一个byte
的值short
:两个byte
的值long
:四个byte
的值string
:字符串
test
- 测验值。用于测验
offset
下的type
是否是这个test
值。应用C
语言的数值或字符示意模式。
- 测验值。用于测验
message
- 用于显示测验后果的信息显示。
如果
type
为数值类型,那么其前面可增加&value
,示意先与前面的test
值进行'与'操作,再进行比拟。如果type
为字符串类型,则其后可跟/[Bbc]*
,/b
示意疏忽空格,/c
示意疏忽字母大小写。
如果test
的值为数值类型,能够数值前增加=
,<
,>
,&
,^
,~
,别离示意相等、小于、大于、与操作、异或操作、取反操作。
如果test
的值为字符串类型,能够在其前增加=
、<
、>
。
例如,ELF文件的magic示意为:# ELF#0string ELF ELF 0 string \177ELF ELF>4 byte 1 32-bit>4 byte 2 64-bit>5 byte 1 LSB>5 byte 2 MSB>16 short 0 unknown type>16 short 1 relocatable>16 short 2 executable>16 short 3 dynamic lib>16 short 4 core file>18 short 0 unknown machine>18 short 1 WE32100>18 short 2 SPARC>18 short 3 80386>18 short 4 M68000>18 short 5 M88000>20 long 1 Version 1>36 long 1 MathCoPro/FPU/MAU Required
发现没有,这个magic
切实不是人类可能轻易读懂的,Linux
内核提供了libmagic
库用来解析magic
文件,然而我尝试了CentOS 7
和Ubuntu20.04
,都没有胜利将程序跑起来(gcc
编译报告找不到magic.h
),而我的需要是要求一种比拟通用的办法,不仅要求在Linux
上能够工作,还要在Windows
和AIX
上有比拟好的体现,因而,希图实现一套相似file
的原理的路行不通。
那么,就没有一种比拟通用的计划来实现对文件类型的判断吗?
网上很多材料说能够依据文件的字符来判断,如果文件中蕴含\x00
,则肯定是二进制或压缩文件,否则的话就是一般文本文件。
在大多数时候,这个规定是成立的。然而如果一般文本文件的编码是UTF-16
或者UTF-32
,则又要哭晕在厕所了。
因而,这种计划不靠谱。
非凡文件的header
libmagic
的思路,说白了,就是依据文件头的编码进行判断,也就是说,只有咱们晓得某些非凡的文件头编码,对这些非凡的文件头进行匹配,如果能匹配上,就代表它是非凡文件,否则的话,就是一般文本文件,依照这个思路,也能实现libmagic
库一样的成果。
在各种类型文件头规范编码这篇文章里,列举了一些常见的文件头编码。比方常见的jar
包、rar
、zip
压缩文件,都是以504B0304
结尾,而Linux
下的二进制文件,包含.o
,.a
,.so
,以及coredump
文件,都是属于ELF
文件,文件头都是7F454C46
。然而windows
的可执行文件结尾却是504B0304
,AIX
零碎简单些,但结尾三个字节根本都是01DF00
。因而,依据这些,就能够做很多辨别了。
事实上,对于windows
零碎来说,依据后缀其实就能辨别进去,而Unix
零碎的约定俗成的后缀规定也能辨别出很多的文件,比方一个文件后缀为.rpm
,你无论如何不会将其当成文本文件,看到.o
就晓得是二进制指标文件,.so
是动态链接库。比拟有歧义的可能只是一些可执行文件,比方ls
、grep
、a.out
这些后缀不代表实际意义的文件。
因而,咱们的思路也就明确了,分两个步骤,首先能够大抵依据文件后缀辨别出一些特地显著的二进制文件、压缩文件,而后针对文件的header
做进一步的辨别。
C语言代码实现
代码实现如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>typedef int boolean;#define FALSE 0#define TRUE 1/*列举一些常见的文件头,能够自行扩大,比方放到某个配置文件中*/static const char *with_suffix[] = {".gz", ".rar", ".exe", ".bz2", ".tar", ".xz", ".Z", ".rpm", ".zip", ".a", ".so", ".o", ".jar", ".dll", ".lib", ".deb", ".I", ".png",".jpg", ".mp3", ".mp4", ".m4a", ".flv", ".mkv", ".rmvb", ".avi", ".pcap", ".pdf", ".docx", ".xlsx", ".pptx", ".ram", ".mid", ".dwg", NULL};/*判断某个字符串是否领有指定后缀*/static boolean string_has_suffix(const char *str, const char *suffix) { int n, m, i = 0; char ch = '\0'; if (str == NULL || suffix == NULL) { return FALSE; } n = strlen(str); m = strlen(suffix); if (n < m) { return FALSE; } for (i = m-1; i >= 0; i--) { if (suffix[i] != str[n - m + i]) { return FALSE; } } return TRUE;}/*判断文件是否具备非凡后缀*/static boolean file_has_spec_suffix(const char *fname) { const char **suffix = NULL; suffix = with_suffix; while (*suffix) { if (string_has_suffix(fname, *suffix)) { return TRUE; } suffix++; } return FALSE;}/*判断文件是否具备非凡文件头*/static boolean file_has_spec_header(const char *fname) { FILE *fp = NULL; size_t len = 0; char buf[16] = {0}; int i = 0; boolean retval = FALSE; if ((fp = fopen(fname, "r")) == NULL ){ return FALSE; } len = sizeof(buf) - 1; if (fgets(buf, len, fp) == NULL ) { return FALSE; } if (len < 4) { return FALSE; }#if defined(__linux__) //ELF header if (memcmp(buf, "\x7F\x45\x4C\x46", 4) == 0) { return TRUE; }#elif defined(_AIX) //executable binary if (memcmp(buf, "\x01\xDF\x00", 3) == 0) { return TRUE; }#elif defined(WIN32) // standard exe file, actually, won't go into this case if (memcmp(buf, "\x4D\x5A\x90\x00", 4) == 0) { return TRUE; }#endif if (memcmp(buf, "\x50\x4B\x03\x04", 4) == 0) { //maybe archive file, eg: jar zip rar sec. return TRUE; } return FALSE;}/*测试程序* 从命令行输出一个文件,返回该文件的类型*/int main(int argc, const char **argv) { if (argc < 2) { printf("usgae: need target file\n"); exit(-1); } const char *fname = argv[1]; if (file_has_spec_suffix(fname)) { printf("file %s have special suffix, maybe it's a binary or archive file\n", fname); } else if (file_has_spec_header(fname)) { printf("file %s have special header, maybe it's a binary or archive file\n", fname); } else { printf("file %s should be a text file\n", fname); } return 0;}
运行后果如下所示,能够比照file命令,做一个参考:
[root@ck08 ctest]# gcc -o magic magic.c [root@ck08 ctest]# ./magic ~/anaconda-ks.cfgfile /root/anaconda-ks.cfg should be a text file[root@ck08 ctest]# ./magic ~/tls.pcapfile /root/tls.pcap have special suffix, maybe it's a binary or archive file[root@ck08 ctest]# ./magic ~/zlib-1.2.11.tar.gzfile /root/zlib-1.2.11.tar.gz have special suffix, maybe it's a binary or archive file[root@ck08 ctest]# ./magic /usr/bin/grepfile /usr/bin/grep have special header, maybe it's a binary or archive file[root@ck08 ctest]# ./magic kafka_2.12-2.8.0.jarfile kafka_2.12-2.8.0.jar have special suffix, maybe it's a binary or archive file[root@ck08 ctest]# ./magic kafka_2.12-2.8.0.jar.1file kafka_2.12-2.8.0.jar.1 have special header, maybe it's a binary or archive file
参考文档
- https://linux.die.net/man/5/m...
- https://man7.org/linux/man-pa...
- https://www.cnblogs.com/gwind...