yanbin's Blog
shell 脚本中,程序的标准输出重定向到 FIFO, 需要注意的问题
remain blocked.
shell脚本使用 timeout + wait 完成: 超时退出执行,等待执行完毕并处理执行结果
timeout time sub cmdresp_topic > cmdresp_record while seq in read seqs: seq = parse_seq(seq) message = create_message(seq) pub cmd_topic message record(message, msg_record) // do something, maybe sleep 1s wait sub parse(cmdresp_record, msg_record)
shell 代码是:
tmp_file="$(mktemp) rm -f $tmp_file mkfifo $tmp_file # terminate $MOSQUITTO while $TIMEOUT period timeout $TIMEOUT \ $MOSQUITTO_SUB -t $topic_resp --cafile $CA --cert $CERT --key $KEY > $tmp_file & cat $tmp_file | cut -d',' -f1 | cut -d':' -f2 | sed -n 's/\"//gp' >> $SUB_LOG & N=0 for line in $(cat $SNS_FILE) do sn="$(echo "$line" | cut -d'&' -f1)" seq="$($LOOKUP_TOPIC $sn)" name="$(printf "%s%.4d" $sn $seq)" topic="router/$name/cmd" N=$((N+1)) mid="$(date +%Y%m%d%H%M%S)-$N" message=makemsg $mid $MOSQUITTO_PUB -t $topic --cafile $CA --cert $CERT --key $KEY -m "$message" sw="$(echo "$line" | cut -d'&' -f2)" record="$(date +%Y%m%d:%H),$sn,$sw,1" [ $? -eq 0 ] && echo "$record" >> $PUB_LOG [ $((N % 10)) -eq 0 ] && sleep $SLEEP_TIME done wait # wait $MOSQUITTO_SUB and cat $tmpfile terminaation rm -f $tmp_file if [ -f $SUB_LOG -a -f $PUB_LOG ]; then tmp_file="$(mktemp -p $RECORD_DIR)" for line in $(cat $SUB_LOG) do sed "s/\(.*$line.*\),1/\1,0/g" $PUB_LOG > $tmp_file cp $tmp_file $PUB_LOG done rm -f $tmp_file fi
Lua 调用的 C 函数保存 state 的两种方式: Storing State in C Functions 笔记
/* variable with a unique address */ static char key = 'k'; /* store a string */ lua_pushlightuserdata(L, (void *)key); /* push address */ lua_pushstring(L, myStr); /* push value */ lua_settable(L, LUA_REGISTRYINDEX); /* registry[&kye] = myStr */ /* retrieve a string */ lua_pushlightuserdata(L, (void *)key); /* push address */ lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */ const char *myStr = lua_tostring(L, -1); /* convert to C string */
/* variable with a unique address */ static char key = 'k'; /* store a string */ lua_pushstring(L, myStr); lua_rawsetp(L, LUA_REGISTRYINDEX, (void *)&key); /* retrieve a string */ lua_rawgetp(L, LUA_REGISTRYINDEX, (void *)&key); const char *myStr = lua_tostring(L, -1);
以上这些创建 key 以访问 table 规则对普通 table 也适用。
static int counter(lua_State *L); int newCounter(lua_State *L) { lua_pushinteger(L, 0); /* 使用 counter 作为 base function 创建 C closure, 讲 stack TOP N 作为 upvalues */ lua_pushcclosure(L, &counter, 1); return 1; }
static int counter(lua_State *L) { int val = lua_tointeger(L, lua_upvalueindex(1)); lua_pushinteger(L, ++val); lua_pushvalue(L, -1); /* duplicate new vlaue */ lua_replace(L, lua_upvalueindex(1)); return 1; }
#define luaL_newlib(L, lib) \ (luaL_newlibtable(L, lib), luaL_setfuncs(L, lib, 0)) 关键点是 luaL_setfuncs() 这个函数讲 statck top N 个 value 作为 upvalues 指定给 library 中的函数。 luaL_newlib() 定义中这个 N 是 0, 这个函数是用不成了。不过可以用如下方式: /* create library table */ luaL_newlibtable(L, lib); /* create shared value */ lua_newtable(L); /* add functions in list 'lib' to the new library, * sharing previous table as upvalue */ luaL_setfuncs(L, lib, 1);
参考:
Programming in Lua third edition 28.3
为什么使用 do {} while(0)
有些宏定义含有多行代码,一般使用 do {} while(0) 把代码放在 'do' 和 'while' 之间的 '{}' 中。
#define foorbar(msg, callback) do {\ struct Task __task = msg_to_task((msg)); \ if (__task != NULL) { \ process(__task, (callback)); \ char *__msg = task_to_msg(__task); \ if (__msg != NULL) \ { send_msg(__msg); \ free(__msg); \ } \ destroy_task(__task); \ } while (0)
这样用的原因是:
1.符合 C/C++ 语法习惯。
每条语句后面都一个';', 而 do {} while 语句后面也要加一个 ';'.
2.避免出现语法错误。
不用 do {} while(0) 把多行代码包起来,在 if else 语句中就会有语法错误,例如:
#define foorbar(a, b) foor((a)); bar((b)) if (something) /* 以下有语法错误 */ foorbar(3, 2); else // do something
仅仅使用 '{}' 把多行代码包起来,如果在调用宏之后加 ';', 也会有语法错误。
#define foorbar(a, b) {\ foor((a)); bar((b));\ } foorbar(3, 2); // 此处有语法错误 /* 编译器提示: * error: expected ‘;’ before ‘}’ token * 如果不加 ';', 不会有语法错误但是这样不符合 C/C++ 的语法习惯 */
3.do {} while(0) 可以根据条件跳出执行。
#define foorbar() do {\ struct condition __cond; \ if (__cond.wait_cond()) \ break; // 条件发生退出执行 \ // 条件没有发生 do something } while(0)
4.私以为 do {} while(0) 可以保证代码执行并且只执行一次。
5.需要注意的地方。
(a)宏定义时用 '\' 连接多行语句;
(b)宏定义中定义变量,注意与外部变量名字冲突,不然原本希望用外面的变量,却用了新定义的变量。
(c)有些编译器会优化掉 do {} while(0); 直接展开 '{}' 内的代码, 如(b)所描述,此时会出现语法错误。
FIXME: 如果内部有 'break' 并且 'break' 的执行依赖运行时条件,编译器就不会优化掉 do {} while(0); 了。
举例:
#define foorbar() do {\ struct condition cond; \ if (cond.wait_cond()) \ break; // 条件发生退出执行 \ // 条件没有发生 do something } while(0) struct condition cond; // do something foobar(); // 到底用的是哪一个 cond?
#define foorbar(a, b) do {\ const char *something = get_something(a, b); \ } while(0) const char *something; // do something foorbar(3, 9); // 如果编译器优化掉了 do {} while(0); 这里有语法错误。
感谢 老猫,mike2,MovableType@源赖朝 三位网友。
参考:
do { … } while (0) — what is it good for?
do{}while(0) 的作用
Linux 系统中使用 inotify 监视文件或目录的改变
0.注意事项
int inotify_init();
int inotify_add_watch(int fd, const char *name);
int inotify_rm_watch(int fd, int wd);
int inotify_init1(int mask);
这些是一系列的 linux 系统调用;
前三个是 linux 2.6.13 引入的, 最后一个是 2.6.27 引入的。
但是一些 C libray(C 语言实现库), 并没有定义这些系统调用的封装。
可以用 syscall(__NR_inotify_init); 这样的形式调用。
__NR_inotify_init 是系统调用号,可以在 unistd.h 中看到。
有些 SDK 中的内核配置没有默认的选定对 inotify 的支持。
可以在 linux 配置中的 kernel setup:
fs-->
[] support inotify for user space
选上对些系统调用的支持。
如果内核没有对这些系统调用的支持,
int fd = syscall(inotify_init) 总是返回 89,
read(fd, buff, sizeof(buff)) 会返回 -1, errno 被设置为 "Bad file descriptor"。
inotify 会监视目录下所有文件。
inotify 并不自动的递归监视目录下的子目录,需要程序员自己完成这样的工作。
1.简介
使用这些 API 可以监视文件系统的 events.
当文件或者目录有改变时,内核产生 inotify events, 用户使用这些 API 获取关注的 events.
不同于陈旧的 dnotify API, inotify API 既可以监视 files 也可以监视 directories.
监视一个目录,不仅可以获取目录自身改变的 event(e.g. 从目录移除文件),也可以监视目录内文件内容改变产生的 event.
另外:称这些函数为 API 是因为它封装了 system call。每一个函数对应一个 system call.
2.每个 API 的作用与使用方式。
inotify_init(2) 用来获取一个文件描述符,这个文件描述符是一个 inotify instance 的引用。
用户通过这个实例建立了一条从内核获取文件系统 events 的渠道或者说连接。
每个 inotify 实例中维护着一个 watch list, 其中的每个 item 对应一个文件/目录。
获取 event 的方式是 read() inotify_init(2) 返回的文件描述符。
若这个文件描述符没有设置为 NONBLOCK, read() 会阻塞,read() 返回时,就是文件系统 events 到来了。
既然类似于「连接」当然就可以应用 select() or poll() 于 inotify 文件描述符了。
这也正是 inotify 系统的优点之一。
当与 inotify 实例关联的文件描述符关闭时,内核释放与该实例相关的资源,并且释放该实例。
FIXE: 为什么 man 手册中讲:所有引用这个 inotify 实例的文件描述(s)? 一个 inotify 实例为什么会有多个
文件描述符引用?
(a)这个文件描述符号可能会被 dup() 多次。
(b)文件描述符没有设置为 CLOEXEC, 子进程继承了父进程的文件描述符。
inotify_init(2) 返回的文件描述符可以使用 fcntl(2) 设置为 nonblock 以及 close-on-exec.
inotify_init1(2) 则提供了便利,在返回文件描述符之前,将它上设置为 nonblock 和/或 close-on-exec;
inotify_init1(2) 接受一个 flags 参数指定为 IN_NONBLOCK, IN_CLOEXEC, 或者 IN_NONBLOCK | IN_CLOEXEC;
flags 参数为 0 时它与 inotify_init(2) 别无二致。
inotify_add_watch(2) 用于向一个 inotify 实例的 watch list 追加 item,或用于改变某个 item.
指定期望监视的文件或目录——使用绝对或相对路径——, 指定期望关注的文件系统 events。若指定的文件或目录不在 inotify 实例的
watch list 之中,内核会创建一个 watch item 追加至 watch list 之中。
若指定的文件或者目录已经存在于 wach list 中,inotify_add_watch(2) 直接返会与文件或目录对应的文件描述符。
若指定的 events 不同与之前的,则重新设置对文件或目录的监视,改变或追加期望获得的 events.
NOTE: 这一改变不会清除已经产生却没有读取的 events.
inotify_watch() 返回的 watch fd 的用处之一: 指定给 inotify_rm_watch() 从 inotify 实例的 watch list
移除某个 item, 从而移除对某个文件或目录的监视。
也就是说,可以删除或更新某个 watch item,从而移除或更新对某个文件或目录的引用。
3.使用 bit mask 标识 inotify event, 列表如下:
IN_ACCESS 文件被访问,有读——read(2)——操作。例如: cat filename.txt
IN_ATTRIB 文件元数据发生改变,例如:
permissions, timestamps, extended attributes,link count(since Linux2.6.25), UID, GID, 等等。
IN_CLOSE_WRITE 文件关闭,并且发生写操作。例如: echo "add data" >> filename.txt
IN_CLOSE_NOWRITE 文件关闭,但是没有发生写操作。例如:open(filename, O_WRONLY); close(fd);
IN_CREATE 目录中有新的文件或目录创建。例如: touch dirname/filename
IN_DELETE 目录中有文件被删除。例如:rm dirname/filename
IN_DELETE_SELF 监视的文件或目录本身被删除。
IN_MODIFY 文件发生改变。FIXME: 什么改变会引发这个 event? 写操作会吗?
IN_MOVE_SELF 监视的文件或目录本身被移动。
IN_MOVED_FROM 从目录中移出一个文件或目录。
IN_MOVED_TO 移动一个文件或目录到目录中。
IN_OPEN 文件打开操作发生。
指定给 inotify_add_watch(2) 的第二个参数,就是上面的一个或多个 bit mask(s);
从而可以得到 intofiy events.
4.inotify event 数据结构:
struct inotify_event { int wd; /* watch 文件描述符 */ uint32_t mask; /* Mask of events */ uint32_t cookie; uint32_t len /* size of name filed */ char name[]; /* optional null-terminated name */ };
5.解析 events.
使用 read() 读取 events 数据到 buff 中,从 buffer 中解析出 events.
NOTE:
1)event->len 是 size of name, 不是 size of event;
2)read() 不保证完整的读取了最后一个 event, 甚至不保证完整的读取了一个 event;
需要比较 size of buff 中剩余有效数据 与 sizeof(inotify_event);
然后再比较 size of buff 中剩余有效数据 与 sizeof(inotify_event) + event->len;
3)读取 events 到 buff 中再解析,为了尽可能一次性读取更多的 event;
#define BUFF_SIZE ((sizeof(struct inotify_event) + NAME_MAX + 1)*5) #define MIN_EVENT_SIZE (sizeof(struct inotify_event) char buff[BUFF_SIZE]; char pos = buff; ssize_t readn; ssize_t data_size; int index; const struct inotify_event *event = NULL; for (; ;) { int nready; int maxfd = watchfd + 1; fd_set readset; FD_ZERO(readset); FD_SET(watchfd, &maxfd); nready = select(maxfd, &readset, NULL, NULL, NULL); if (nready < 0) { perror("select"); } else if (FD_ISSET(watchfd, &readset)) { __read_data__: readn = read(watchfd, pos, sizeof(buff)-(pos-buff)); if (readn == -1) { if (errno == EGAIN) { goto __read_data__; } else { goto __error__; } } else { data_size = readn + (pos - buff); pos = buff; index = 0; while (index < data_size) { event = (const struct inotify_event *)&pos[index]; ssize_t size = sizeof(*event) + event->len; ssize_t remain = data_size - index; if (remain < size) goto __incomplete__; struct inotify_event *eventobj = malloc(size); if (eventobj != NULL) { memset(*event_obj, 0, sizeof(*event_obj)); memcpy(eventobj, pos[index], size); /* Process inotify event */ process(eventobj); free(eventobj); } index += size; remain = data_size - index; if (remain <= MIN_EVENT_SIZE) goto __incomplete__; __incomplete__: memmove(buff, pos[index], remain); pos = &buff[remain]; goto __read_data__; } goto __read_data__; } } }
4月5号更新:之前理解错误,谬误太多。
感谢 依云 指点「一个 inotify 实例为什么会有多个文件描述符引用?」问题。
5月3号更新:增加解析 events 伪代码。
参考:
Linux Programmer's Manual INOTIFY(7)
读取 /dev/urandom or /dev/random 生成随机数
/dev/random
可生成高随机性的公钥或一次性密码本。若熵池空了,对 /dev/random
的读操作将会被阻塞,直到收集到了足够的环境噪声为止[3]。这样的设计使得
/dev/random
是真正的随机数发生器,提供了最大可能的随机数据熵,建议在需要生成高强度的密钥时使用。/dev/random
的一个副本是 /dev/urandom
("unlocked",非阻塞的随机数发生器[4]),它会重复使用熵池中的数据以产生伪随机数据。这表示对
/dev/urandom
的读取操作不会产生阻塞,但其输出的熵可能小于 /dev/random
的。它可以作为生成较低强度密码的伪随机数生成器,不建议用于生成高强度长期密码。
感谢 依云 教我用节省熵池的方法。
自签发 SSL/TLS 证书的方法以及遇到的一些问题
0. 目的
如果签章是正确的,而用户可以相信签署者,之后用户就知道他们可以使用这个密钥,来与密钥的拥有者进行通信。在X.509中规范了这个认证的过程与标准。①
[ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = ShangHai localityName = Locality Name (eg, city) localityName_default = ShangHai 0.organizationName = Organization Name (eg, company) 0.organizationName_default = My Company Name organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = May Project
1. 生成 CA root key (根密钥); CA root key 非常重要。
a) 不仅签发或更新 CA root 证书需要。
b) 签发通信双方数字证书时也需要。
$ openssl genrsa -des3 -out root-ca.key 1024
0) 指定了 -des3 选项,openssl 要求输入 pass phrase;
pass phrase 是 root key 的密码;
CA 签发数字证书时需要输入这个密码,算是一种防范措施吧。
程序使用证书和 key 时, 一般会要求输入密码,可以作为用户密码用吗?
2) FIXME: 1024 是 key 的长度目前也支持 2048, 这两个值有很大的区别吗?
key 的长度为 2048 会耗费更多的 CPU 资源吧。
$ openssl req -new -x509 -days 3650 -key root-ca.key -out root-ca.crt -config openssl.cnf
1) 这条命令将两个步骤合二为一。
a) 使用 private key 生成 certificate request; 需要填写证书持有者的信息;
b) 使用 certificate request 签发证书(singed)或自签发证书(self-signed);
任意创建一个目录,创建配置文件(比如: openssl.cnf),也可以签发根证书。
$ mkdir demoCA && cd demoCA
$ touch index.txt
$ echo 01 > serial
$ mkdir private #指定 private_key=/path/to/demoCA/myCA/private/root-ca.key
$ mkdir myCA # 指定 CA_default:dir=/path/to/demoCA/myCA
$ openssl x509 -noout -text -in root-ca.crt
签发服务器证书
0.生成一个 private key, 并且直接用新生成 private key 创建一个 certificate request.
NOTE: -nodes 参数,使用这个参数生成的 key 不需要密码
$ openssl req -newkey rsa:1024 -keyout server01.key -nodes -config openssl.cnf -out server01.req
Country Name (2 letter code) [CN]: State or Province Name (full name) [ShangHai]: Locality Name (eg, city) [ShangHai]: Organization Name (eg, company) [My Company Name]: Second Organization Name (eg, company) [My Company Name]: Organizational Unit Name (eg, section) [My Project]: Common Name (eg, YOUR name) []:*.example.com #CN Email Address []:server01@example.com
NOTE: CN(common name) 可以是 hostname,IP 或者 domain, 与主机信息对应;
也可以用这种形式: *.example.com
CN 指的是 common name, 别与中国的代码弄混了。
# Using configuration from openssl.cnf
Enter pass phrase for /etc/pki/CA/private/root-ca.key: # 输入root key 文件的密码 DEBUG[load_index]: unique_subject = "yes" Check that the request matches the signature Signature ok Certificate Details: Serial Number: 2 (0x2) Validity # FIXME: 默认是一年的有效期吗? # 默认是 365 days 的有效期, 润年会是 366 days. Not Before: Apr 29 15:18:20 2015 GMT Not After : Apr 29 15:18:20 2016 GMT Subject: countryName = CN stateOrProvinceName = ShangHai localityName = ShangHai organizationName = My Company Name organizationName = My Company Name organizationalUnitName = My Project commonName = *.example.com emailAddress = server01@example.com ....... Certificate is to be certified until Apr 29 15:18:20 2015 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated
c)-nodes 不需要 key 文件有密码
Country Name (2 letter code) [CN]: State or Province Name (full name) [ShangHai]: Locality Name (eg, city) [ShangHai]: Organization Name (eg, company) [My Company Name]: Second Organization Name (eg, company) [My Company Name]: Organizational Unit Name (eg, section) [My Project]: Common Name (eg, YOUR name) []:client001 EmailAddress []: client001@example.com
Enter pass phrase for /etc/pki/CA/private/root-ca.key: DEBUG[load_index]: unique_subject = "yes" Check that the request matches the signature Signature ok Certificate Details: Serial Number: 2 (0x2) Validity: Not Before: Apr 29 15:18:20 2015 GMT Not After : Apr 29 15:18:20 2016 GMT Subject: countryName = CN stateOrProvinceName = ShangHai localityName = ShangHai organizationName = My Company Name organizationName = My Company Name organizationalUnitName = My Project commonName = client001 emailAddress = client001@example.com ....... Certificate is to be certified until Apr 29 15:18:20 2015 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated
参考:
类型安全的 max(), min()
#define max(a, b) \ ({ __typeof__ (a) (_a) = (a); \ __typeof__ (b) (_b) = (b); \ _a > _b ? _a : _b; }) #define min(a, b) \ ({ __typeof__ (a) (_a) = (a); \ __typeof__ (b) (_b) = (b); \ _a < _b ? _a : _b; })
Debian 7.6 Wheezy 安装 Intel® Wireless 7260 wifi 模块驱动和固件的方法。
0. 内核以及发行版 Version 信息
Debian 7.6 Wheezy
Linux fyp-tp 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 GNU/Linux
1. 获得 PCI device numbric ID
$ lspci -nn # 可以显示设备号和subsystem ID
....
04:00.0 Network controller [0280]: Intel Corporation Device [8086:08b2](rev 6b)
在下面的网站可以根据设备号(0x808) 和 subsystem ID(0x08b2) 查找到设备型号:
http://pci-ids.ucw.cz/read/PC
可知 wifi 模块的型号是: Intel Wireless 7260
2. google 可知这个 wifi 模块使用 iwlwifi wireless driver。
这个驱动包在 Debain nofree 源中。 使用 Debian nofree 源:
$ sudo echo '# Debian 7 "Wheezy"' >> /etc/apt/sources.list
安装完驱动后,要安装相应的固件,可以在
http://wireless.kernel.org/en/users/Drivers/iwlwifi
找到相应的固件包, 解压后使用 root 用户安装:
$ cp iwlwifi-*.ucode /lib/firmware
3. 但是,Debian提供的 iwlwifi 驱动模块是 3.2.60 Linux Kernel 版本的,
iwlwifi 驱动支持 7260 wifi 模块要求: 最低 Linux Kernel 版本 3.10+.
但是,在 Debian 升级 Linux Kernel 不是一个好的解决方案。
好消息是:可以使用 linux kernel backports 来编译本应该运行在高版本内核的驱动,
编译的得到驱动可以在低版本的内核上运行, 并且支持 wifi 模块。
"If you need to use the current driver on an older kernel, you can use
the compat-drivers project for that. See https://backports.wiki.kernel.org/
for more information."
4. 下载 backports-3.14-1 在 http://drvbp1.linux-foundation.org/~mcgrof/rel-html/backports/
需要打一个 patch: http://www.mail-archive.com/backports@vger.kernel.org/msg01790.html
配置以及安装,参考: https://backports.wiki.kernel.org/index.php/Documentation
#可以只配置 wifi 模块的驱动:
$ make defconfig-wifi
#可以选择只编译 iwlwifi
$ make menuconfig
[*] Wireless LAN --->
Intel Wireless WiFi Next Gen AGN - Wireless-N/Advanced-N/Ultimate-N (iwlwifi)
$ make
$ sudo make install
5. 在 http://wireless.kernel.org/en/users/Drivers/iwlwifi 下载:
iwlwifi-7260-ucode-22.1.7.0.tgz
iwlwifi-7260-ucode-22.24.8.0.tgz
iwlwifi-7260-ucode-23.214.9.0.tgz
三个固件包并且安装到 /lib/firmware
如果没有安装固件的话, dmesg 信息中会有 iwlwifi-7260-7.ucode 找不到之类的信息.
没有测试是否也需要 iwlwifi-7260-9.ucode. (我在安装 iwlwifi 驱动之前就安装了这个固件)