yanbin's Blog

1. 简单 Shell 编程 FAQ

5. mkdir -p 与 install -d 的区别?

install -d, mkdir -p 都可以用来创建目录,并且创建整个路径上缺少的目录。

install -d /path/to/directory
#install 尝试改变 directory 的读写权限为: $(umask) ^ file create mode,
#如果 directory 已经存在,并且属主不是执行 install -d 的用户,install 会有权限错误:
install: cannot change permissions of ‘/path/to/directory’: Operation not permitted

一般来说执行之前会判断目录是否存在,没有判断又依赖 install 执行成功就有问题了。
install -d 一般只有一个 root 用户的嵌入式系统上常见,毕竟不存在权限问题。(不是所有的嵌入式系统都只有一个 root 用户)
install -d 也用在一些 SDK 里,打包程序时一般用 install -d 创建一些目录。

$ mkdir -p /path/to/directory
#完成相同的创建目录工作,并不会尝试改变什么,directory 存在时甚至不会执行任何操作,直接返回成功。
?

6.Shell 脚本'\\NL'续行小结。NL 表示 newline;
(a) '“'括起来的字符串中出现'\\NL';
这种形式的字符串中的 '\\NL', 会被转义,最终作为参数传递给程序/命令的字符串是没有续行符的一行。
echo \"hello \\\r\nworld\"
# 输出 hello world
(b) '‘'括起来的字符串中的 '\\NL';
这种形式的字符串中的所有字符都会以其文本形式对待,'\\NL'不会被转义,传递给程序/命令是会视为两个字符,
一个是'\\',一个是'NL'.
一般不会在'''括起来的字符串中出现'\\NL, sed 和 awk 的代码中例外。

echo 'hello \\\r\nworld'
# 输出 hello \\\r\nworld

(c) 代码续行;\r\n当一行代码过长超过80个字符时,一般会用 '\\NL'的形式换一行,目的是为了阅读方便,一般出现在管道字符 '|'之后,\r\n或者某个参数之前。Shell 在解析代码时,这种形式的'\\NL'也会被转义。

(d) sed, awk 执行的代码中的续行;
如果是用 '''包括的代码,一般来说传递给 sed, awk 时其实是两个字符,这个与(b)是相同。
对于 sed 和 awk 而言,在处理/解析这样的代码时会转义续行符。
这样做的目的也是为了更方便的阅读代码。其实也可以不用续行符。

7.读/写 FIFO 为什么会阻塞?
FIFO 的默认特性:读没有写端的 FIFO 会阻塞;而写没有读端的 FIFIO 也会阻塞。
先启动读端程序,还是先启动写端程序呢?\r\n无论如何要确保:
(a) 写端程序不会因为读端程序不存在或启动不及时而丢失数据。
(b) 确保读端程序启动后写端程序不会因此而没有机会运行。

8.awk 如何在表达式中使用 Shell 变量的值?
使用 awk 的 -v val=val 参数传递 Shell 变量给 awk 并且用于 awk 程序之内。
foobar=7
cat foobar.txt | awk -v foobar=$foobar -F','? '{ printf(\"%d\\n\", $1*foobar)}'
# -v val=val 参数可以有多个:
foo=3
bar=5
cat foobar.txt | awk -v foo=$foo -v bar=$bar -F',' '{printf(\"%d\\n\", 1*foo*bar)}'

9.shell 脚本可以等待一个程序执行完吗?有哪些限制?
使用 wait 命令。
wait 是 bash 的一个内部命令,使用 -n 指定一个或多个 child pid, 等待进程退出并返回其 termination status;
如果指定了多个 pid ,那么就返回最后一个进程的 termination status;
没有指定 pid, wait 等待 Shell 脚本的所有子进程退出执行。
限制是:只能等待 Shell 脚本的子进程退出。

MySQL: Specified key was too long; max key length is 767 bytes

MySQL 使用了InnoDB存储引擎 UNIQUE index 长度限制是 767 byes; 使用了MyISAM存储引擎长度限制是 1000 bytes。
使用了 utf-8 字符集两种引擎下的 vchar 型 UNIQUE index 最大长度分别是 vchar(255) 和 vchar(333),
utf-8 最多使用 3 bytes 表示一个字符: 255 * 3 = 765 ...
使用了 utf-16 字符集两种引擎下的 vchar 型 UNIQUE index 最大长度分别是 vchar(191) 和 vchar(250)。

django 有时遇到这个问题。
应当怀疑使用了 utf-8 之类的字符集并且 Field(unique=true, max_length=greater_than_255);
或者是否有 unique_together = ['field0', 'field1']; field0_max_length + field1_max_length > 255;

参见:mysql-specified-key-was-too-long-max-key-length-is-767-bytes

?


?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

简单 Shell 编程 FAQ(〇)

0.sed 命令如何使用 shell 脚本某个变量的值?
? 使用 '“'括起表达式,或者即不用 '‘' 也不用 '“' 括起表达式。
? 'expression'? 是使用 sed 时常用/常见的方式。
? 可以用 "expression" 的形式给 sed 指定表达式参数。
? shell 对待参数的方式:
? 1)使用 '‘'? 括起来的字符以它的文本形式对待。$ 就是 $ 这个字符,不会对待为展开变量的值;
? 2)使用 '“' 括起来的 '$ '用于展开变量的值,'\' 用来转义,其它字符仍然以文本形式对待;
? 3)执行命令时指定参数而不用'‘' 或'“'? 括起来与使用 '“'类似;
# '$' 在这里用于匹配 '$'字符。
sed 's/A $foobar value/foobar/g' foobar.txt
# $ 展开 foobar 这个变量的值。转义的 \$ 匹配 '$'。
# 参数在传递给 sed 程序时已经完成变量值展开和转义了。
# 完成转义和变量展开的是 shell 而不是 sed.
sed "s/A $foobar \$value/foobar/g" foobar.txt
# 这个与用 '“'括起来的效果是相同的。
sed s/A $foobar \$value/$/g foobar.txt
?
1.sed 如何从文件中直接删除一行或多行?
? 使用 sed 的 d 命令。
# 不熟悉 sed 时一般会写这样的代码。这种方式容易出错且耗费资源。
cat foobar.txt | sed 's/patter to match//gp' > tmp_file.txt
mv tm_file.txt foobar.txt
# sed 的 d 命令 加 -i 参数 可以完成直接修改文件的操作。
sed -i '/pattern to match/d' foobar.txt
# 只输未匹配即没有被删除的行,而不修改文件
sed 'pattern to match/d' foobar.txt
? -i[SUFFIX], --in-place[=SUFFIX]
? edit files in place (makes backup if SUFFIX supplied)
# -i 参数接受一个可选的 suffix, 指定这个 suffix, sed 会修改文件前备份文件
sed -i.bak '/pattern to match/d' foobar.txt
?
2.找出只在一个文件中出现,不在另一个文件中出现的行?
? 使用 diff 命令,并且设置 --LTYPE-line-format="", 并且要求两个文件的行时排序过的。
# 输出在 file1 中出现,不在 file2 中出现的行。
diff --new-line-format="" --unchanged-line-format="" file1 file2
? 基本原理:
? --new-line-format, --unchanged-line-format, --old-line-format 参数分别用来操作 diff 格式化输出:
? (file2 中)新增加的行、没有改变过的行、以及(在 file2 中)删除的行。
? 参数的值是 "",也就是不输出。
? (a)--new-line-format="", 不会输出只在 file2 中出现的行;
? (b)--unchanged-line-format="", 不会输出在两个文件中都出现的行;
? (c)指定了 --new-line-format="", --old-line-format 没有指定值,diff 默认直接输出只在 file1出现 old lines.
?
? 换个思路,假设一个 new_file, 一个 old_file, 都排序过:
# 输出在 new_file 中出现,不在 old file 中出现的行。
# 指定 --old-line-format=“”, 没有指定 --new-line-format,diff 默认直接输出 new line, 没有前导的 '<'.
diff --old-line-format="" --unchanged-line-format="" old_file new_file
? 如果文件没有排序过也可以用 <(sort file1) 的方式排序
diff --new-line-foramt="" --unchanged-line-format=="" <(sort file1) <(sort file2)
?
3.sort 用指定 key (某些字符) 排序?uniq 按每行的前 N 个字符去重?
? sort --key 参数; uniq -w 参数
# 以每行的 1到15个字符为 key 排序。1,15 都是 position, 第一个字符的 position 是 1.
sort --key 1,15 foobar.txt
# uniq 按每行的前 12 字符去重
sort --key 1,15 foobar.txt | uniq -w 12
? sort:
? -k, --key=KEYDEF sort via a key; KEYDEF gives location and type
? KEYDEF is F[.C][OPTS][,F[.C][OPTS]] for start and stop position
?
? uniq:
? -w, --check-chars=N compare no more than N characters in lines
?
4.超时退出一个程序的执行?
? timeout 程序:run a command with a time limit.
# 5 秒后向 command 程序发 SIGTERM 信号,退出 command 的执行。
tiemout 5 /path/to/slow/command with options
? timeout 没有指定 --preserve-status 参数,返回 124.
? timeout 有更灵活的用法。man timeout 可以获得更多参数介绍。

shell 脚本中,程序的标准输出重定向到 FIFO, 需要注意的问题

FIFO, 又称为命名管道 (named pipes)。与 PIPE 不同和相同之处:
1. PIPE 只能用于关联进程之间,比如: 父子进程之间,两个子进程之间。
2. 关于读写:
?? (a) 写一个没有读端的 PIPE,会触发 SIGPIPE(Broken pipe: write to pipe with no readers).
?? (b) 写一个没有读端的 FIFO,没有设置 O_NONBLOCKING 会阻塞,直到有进程读;
??????? 设置了 O_NONBLOCKING, 将会返回 -1.
?? (c) 没有写端时读 PIPE/FIFO 都是会阻塞,而设置了 O_NONBLOKING 读都会返回 -1.
?
由于 FIFO 没有读端写端会阻塞的特性。
在 shell 脚步中,一个程序的标准输出重定向到 FIFO, 即写 FIFO 的程序启动之时或之后,
确保读 FIFO 的程序是否存在并且及时存在了,否则可能会有如下问题:
1.?读 FIFO 的程序没有启动过,写 FIFO 的程序永远阻塞在 FIFO 操作上。
2. 读 FIFO 的程序后来启动了,写 FIFO 的程序可能丢失了大量数据当其阻塞在写 FIFO 操作之上时。
3. 假如写 FIFO 的程序是一个多线程程序,她的产生数据操作写数据操作是分开,并且有内部数据缓存,
?? 当读 FIFO 的程序启动较晚时,会有大量数据要读,而且写 FIFO 程序也有大量数据要写,
?? 这至少会耗费时间。
4. 实在要考虑写 FIFO 程序读 FIFO 程序的启动先后顺序问题,要考虑时间窗口。
5. Linux Programmer's Manual, pipe(7) 给出的设计思想:
?? Applications should not rely on a particular capacity: an application should be designed so that
?? a reading process consumes data as soon as it is available, so that a writing process does not
?? remain blocked.

shell脚本使用 timeout + wait 完成: 超时退出执行,等待执行完毕并处理执行结果

具体需求是:
1.从文件中读取 seq, 使用 pub 程序将 seq 推送给定阅读了 cmd topic 的 peer client.
?? client 将处理结果(message)推送到 cmdresp topic. 这个过程是经过 server 的,可以不考虑。
?? pub 成功后记录 seq.
2. sub 程序订阅 cmdresp topic, 简单处理接收到的消息,并且记录到文件。
3. 对比 pub 成功的 seq 记录与接收到的 message, 获得一个 pub/sub 成功率。
?
问题是:
1. pub 和 sub 分别使用两个不同的管道,pub cmd 成功后并不等待 cmdresp.
?? 甚至可以说: pub 程序并不知道 cmdresp 的存在,甚至不知道 sub 的存在.
2. sub 是阻塞的:没有任何消息也不退出。
3. pub message 到 peer client 到 cmdresp 返回之间的时间是不确定的。
?? 消息在两个管道都有可能出现延迟。
4. sub 程序本身没有超时退出选项。
?
伪代码是:
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 笔记

使用 Lua call C 编程,不免需要保存一些 non-local data 在 C 函数中; non-local data 来自 Lua state.
然而,直接使用 C static/global 变量保存 state 是有问题的: (a) C 变量不能保存一个 generic Lua value;
(b) static/global 变量的方式也无法用于 multiple Lua states, 因为不同 Lua state 调用相同函数/库时,
需要 C 函数保存的 state 是独立不相关的。
?
C API 提供了两个地方来保存 non-local data: registry 和 upvalues.
?
Registry
Registry 是一个 global table, 只能在 C 函数中使用 Lua C API 提供的函数和 pseudo-index 访问。
pseudo-index 就像一个普通的 stack index 一样,不同的是它所关联的 value 并不真正的存放在 stack 之上。
访问 registry 的 psedu-index 定义 LUA_REGISTRYINDEX. 对 registry 的访问就像是对 stack 访问一样,
Lua C API 提供的大多数函数都可以用于 registry —— 指定 LUA_REGISTRYINDEX 作为 index ——,
只有 lua_remove(), lua_insert() 这些操作 stack 本身的函数除外。例如:
lua_getfiled(L, LUA_REGISTRYINDEX, "SOME_KEY");
?
既然 registry 是一个 regular table, 那么就可以用所有 index 访问它,当然 nil 是不行的。
使用 registry? index 的选用一定要谨慎,毕竟 registry 是全局的,而且所有库和函数都可以共享的 table;
为避免 key 冲突,应当:
(a) 不要使用 number 作为 key;
(b) 使用 string 作为 key, 尽量加上一些特殊的前缀,比如: LIBRAY_NAME_ 这样的;
(c) 使用 reference system;
?
Reference system 是 auxilibrary 提供的一系列函数,使用这些函数可以保证存储 value 到 table
无须关心 key 的唯一性。如下方式可创建一个新的 reference:
int r = luaL_ref(L, LUA_REISTRYINDEX);
这个函数 pop 一个 value 从 stack, 并用新的 index 保存到指定的 table 中,这里 LUA_REISTRYINDEX
关联到 registry, 所以这个调用可保存一个 value 到 registry.
luaL_ref() 函数会将 key 返回,用于以后的对 table 的访问以及 release reference.
luaL_rawgeti(L, LUA_REGISTRYINDEX, r);
luaL_unref(L, LUA_REGISTRYINDEX, r);
?
指定给 luaL_ref() 的 value 是 nil —— 或者说栈顶的 valu 为 nil ——,luaL_ref() 返回 LUA_REFNIL,
使用 LUA_REFNIL 调用 luaL_unref() 和 lua_rawgeti() 都没有任何效果,算得上人畜无害。
另外一个 reference system 定义的常量是 LUA_NOREF, 这个常量保证不同于任何 valid reference,
可用来判断一个 reference 是否为 invalid reference.
?
reference system 有用之处就在于: 我们不能使用 C pointers 指向任何一个 lua object(包括 string),
这样的引用是未定义行为,但是我们可以使用 reference system 将对象存储在 registry 中,
并且维护这个 reference, 就像使用 C pointer 一样。
?
另外一种选择 key 的方式是:使用 C library 中的 static 变量的地址作为 key(的创建源)。
C 链接器可以保证这些变量的地址是唯一的, 常量的大概不行,比如:字符串常量,宏定义的常量。
不过直接用 static 变量的地址作wei index 是错的,得玩点黑魔法:
lua_pushlightuserdata();或 lua_rawsetp(), lua_rawgetp();
?
示例代码:
/* 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 */
?
使用 lua_rawsetp(), lua_rawgetp() 的方式
/* 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 也适用。
?
?
Upvalues
Registry 提供了 gblobal 变量给 C function 而 upvalues 机制更象是提供了 static 变量给 C function.
Upvalues 机制主要实现了将 upvalues 与 C functions 相关联,每一个 upvalue 可以保存一个 Lua value.
主要方式是使用 C function 创建 C closure, 并且提供 upvalues 给 C closure.
C closure 的使用与 Lua closure 非常相似,实际 Lua 代码中就是当做 closure 来使用的。
Upvalues 机制可以使用相同的 C function clode 以及不用 upvalues 创建多个不同的 closure.
?
示例代码:
首先是一个工厂函数,这个函数用来创建 C closure.
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;
}
?
黑魔法函数是 lua_pushcclosure(), 其第二个参数制定用来创建 C closure 的 base function,
第三个参数指定 stack TOP N 作为 upvalues.
NOTE TAHT:必须保证 stack 中前 N 个 value 是要作为 upvalues 的,不然是错误。
?
看看 base function 中是如何访问 upvalues 的。
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;
}
?
访问 upvalues 也是用 pseudo-index 的方式,真正访问的值并不保存在 stack.
lua_upvalueindex(N) 访问一个 pseudo-index 关联于第 N 个 upvalue。
NOTE THAT:
绝对不能用负数访问 lua_upvalueindex(), 为了避免这样错误可以用 luaL_argcheck() 函数检查参数。
?
?
Shared upvalues
C closure 间不能像 Lua closure 那样共享 upvalues;
为了做到这一点可以让 C closure 间共享一个 table 作为 upvalue, 从而共享了 upvalues.
具体黑魔法要先看 luaL_newlib() 的定义, 这是一个宏定义:
#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)

http://en.wikipedia.org/wiki/Inotify

http://zh.wikipedia.org/wiki/Inotify

读取 /dev/urandom or /dev/random 生成随机数

< /dev/urandom tr -dc A-NP-Za-kmnp-z2-9 | head -c 8
?获取一个 8 位的随机数,除了 0, 1, o,O, l 之外.
?
tr -dc a-z < /dev/urandom 从 /dev/urandom 读入数据并且把所有的小写字母输出。
-d, delete characters in SET1 (a-z);
-c, use the complement of SET1
一个删除,一个补全,最终的效果就是获得读入数据中的所有小写字母。
tr 是从标准输入读入数据,直到遇到 EOF 才会停止,但是 /dev/urandom 是没有 EOF 的。
head -c 8 的作用很明显,就是获得 tr 标准输出中的前 8 个字符,这样这条命令/程序,才可以退出。
?
节省熵池的方法, 生成了长度为 12 的随机数:
dd if=/dev/urandom bs=1 count=6 2> /dev/null | od -t x1 | tee test | sed '2d;s/^0\+ //;s/ //g'
?
生成一个随机 MAC 地址, 节省熵池的方法:
dd if=/dev/urandom bs=1 count=6 2> /dev/null | od -t x1 | sed '2d;s/^0\+ //;s/ /:/g'
?
不建议使用的方法:
< /dev/urandom tr -dC a-f0-9 | head -c 12 | sed s/../\&:/g | head -c 17
?
/dev/urandom:u 是 unlocked 的意思.
/dev/urandom 是 /dev/random 的非阻塞副本。
/dev/random 是 Linux/Unix ?操作系统中的一个设备文件,用来作为随机数发生器/伪随机数发生器。
它允许程序访问来自设备驱动程序或其它来源的背景噪声,Linux是第一个以背景噪声产生真正的随机的实现。
?
在 Linux 系统上,有一个存储噪声的熵池,读取时 /dev/random 设备返回小于熵池字节总数的随机字节。
/dev/random 可生成高随机性的公钥一次性密码本。若熵池空了,对 /dev/random 的读操作将会被阻塞,直到收集到了足够的环境噪声为止[3]
这样的设计使得 /dev/random 是真正的随机数发生器,提供了最大可能的随机数据熵,建议在需要生成高强度的密钥时使用。
/dev/random 的一个副本是 /dev/urandom?("unlocked",非阻塞的随机数发生器[4]),它会重复使用熵池中的数据以产生伪随机数据。
这表示对/dev/urandom的读取操作不会产生阻塞,但其输出的熵可能小于 /dev/random 的。
它可以作为生成较低强度密码的伪随机数生成器,不建议用于生成高强度长期密码。

感谢 依云 教我用节省熵池的方法。

为 Android 程序创建 CA keystore 以及 self-signed keystore 的方法

为什么 Android 程序需要 CA KeyStore?
1. 在 Android 程序中建立一条 SSL/TLS 连接时,受信任 CA?(Trusted CAs) 用来验证 server。
? ? Public-Key Infrastructure (PKI)?中有 trust certs 概念,许多网络工具实现了 trusted CA 的使用。
??? 比如: curl, Android?URLConnection.
? ? Android 系统中有一个 trusted CAs list, 包含 100 多个 trusted CAs.
??
2. 建立 SSL/TLS 连接时现有的 trusted CAs 不能验证服务器证书,会引发一个 security exception:
? ? ? ?javax.net.ssl.SSLHandshakeException: ... : Trust anchor for certification path not found.
? ?具体而言,以下这些情况会引发这个问题:
? ? ? (a) The CA that issued the server certificate was unknown;
? ? ? (b)?The server certificate wasn't signed by a CA, but was self signed;
? ? ? (c)?The server configuration is missing an intermediate CA;
?
3. 针对(a) 和 (b) 这两种情况,解决方法是创建 SSL/TLS 连接时使用 Android TurstManager 工具。
? ? 在 Android 程序中 TrustManager 用 KeyStore instance 初始化,而 KeyStore instance 读取/解析
??? BKS/JKS 格式的 KeyStore 文件,获得证书信息。
??
4. 使用 keytool?工具创建 BKS 格式的 KeyStore 文件。
? ? keytool 在多数 linux 发行版中都可以通过 package 管理工具获得。
? ? keytool 创建 BKS 格式的 KeyStore 文件需要用到 BouncyCastle Provider, 这个文件 JRE 不提供,
??? keytool 本身也没有提供,需要下载后通过参数指定。创建 JKS 格式的 KeyStore 不用这个文件。
?
? ? $?keytool -importcert -v -trustcacerts -file "root-ca.crt" -alias root-ca -keystore "root-ca.bks"
?????? -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar"
?????? -storetype BKS -storepass zhelishimia
?
?? 验证 keystore 文件是否正确:
?? $ keytool -list -keystore "root-ca.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider
???? -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass zhelishimima
?
5. 也可以直接用 server certificate 生成 keystore 并且设置为 trusted, 不过这样做不够安全,而且 server
??? 端的证书有改变时还需要生成新的证书。
??
?
有时也客户端程序也用证书标识自己的身份。或者有些 JAVA server 程序中建立 SSL 连接时用 KeyStore
提供证书信息。
1. 使用 openssl 工具转换 X509 格式的 public certificate 和 private key 为 pkcs12 格式的文件。
??? $ openssl pkcs12 -export -in broker.crt -inkey broker.key -out broker.p12 -name client001
?????? -CAfile root-ca.crt -chain
?
2. 使用 openssl 工具和 pkcs12 文件生成 keystore
??? $ keytool -importcert -v -trustcacerts -file "root-ca.crt" -alias root-ca -keystore "root-ca.bks"
?????? -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar"
?????? -storetype BKS -storepass 12345678
?
3. 在 Android 程序中, 建立一条 SSL 连接时可以用 KeyManager 工具指定本端的 public certificate,
??? private key, KeyManager 需要用 KeyStore instace 初始化,KeyStore instance 加载/解析 keystore 文件,
??? 获取 public certificate 以及 private key 信息。
?
?
参考:

???