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 信息。
 
 
参考:

   




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee