今天看到同事写的代码,在多进程之间做互斥时,使用semget()获取信号量的值。其中的变量key通过ftok()获得。
以上的操作都是符合逻辑的,也没有问题。
但是他怕信号量文件被删掉,在ftok()之前,查看了该文件是否存在,并且当文件不存在时,创建文件。
一切看上去都是那么合理。但是ftok()这么使用存在很大的风险。即便是同样的文件名称、路径,也可能会生成的不同key_t类型变量。其实之前我也不是很确定,只是感觉ftok()不会这么傻的像同事认为的那样,只靠一个字符串(路径名)来获取key,仔细看了看手册,里面有如下的描述:
The resulting value is the same for all pathnames that name the same file, when the same value of proj_id is used. The
value returned should be different when the (simultaneously existing) files or the project IDs differ.
注意括号中的simultaneously,我英语不好,之前一直忽略了这个词,刚才查了下,这里指明了文件必须是同时生成的。我的假设在手册里得到了证明。不妨再写个小程序测试一下:
/********** main.c, begin **********/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main()
{
key_t val = 0;
int pro_j = 's';
char path[256] = "/tmp/hello.ftok";
val = ftok(path, pro_j);
printf("ftok() return %u/n", val);
return 0;
}
/********** main.c, end **********/
编译,运行:
~# gcc -g -Wall main.c -o main
~# touch /tmp/hello.ftok
~# ./main
ftok() return 1929382828
此时根据/tmp/hello.ftok生成了一个key。删除文件,但不马上创建文件,先创建两个无关的文件,让它们占用之前的目录树中的节点---文件的大小是相同的,时间会不同,但是时间应该不是关键因素,尝试下其他的方式。
~# rm -f /tmp/hello.ftok
~# touch /tmp/test1.ftok
~# touch /tmp/test2.ftok
~# touch /tmp/hello.ftok
再次调用main,查看结果:
~# ./main
ftok() return 1929382830
哈哈哈哈哈,值真的变了!并且增加了2。查看源码,看看到底是什么左右了ftok()的返回值:
/********** glibc-2.7, sysvipc/ftok.c, begin **********/
#include <sys/ipc.h>
#include <sys/stat.h>
key_t
ftok (pathname, proj_id)
const char *pathname;
int proj_id;
{
struct stat64 st;
key_t key;
if (__xstat64 (_STAT_VER, pathname, &st) < 0)
return (key_t) - 1;
key = ((st.st_ino & 0xffff) | ((st.st_dev & 0xff) << 16)
| ((proj_id & 0xff) << 24));
return key;
}
/********** glibc-2.7, sysvipc/ftok.c, end **********/
函数的源码很简单(这个文件也很简单,总共才38行!),通过__xstat64获取了文件的属性,可以看到,当proj_id不变时,key的值与文件属性中的节点号和文件所属的设备号有关!
或许当你在做简单测试的时候,删掉文件,再创建一个文件,发现生成的key没有变化,但是当程序部署在大型的系统中时,很有可能因为文件的节点号不同,获取到不同的key,导致对数据的保护失败,而这种bug,在写完代码之后,很难发现。
所以我建议,不要在程序中生成ftok()中所指定的文件,而是通过工程人员手工添加此文件。如果发现文件不存在时,程序大可退出,当用户使用的方便性和程序运行的安全相冲突时,还是考虑先程序和数据的安全吧。