UNIX常见问题解答(四)
|
|
|
作者:佚名
来源:InterNet 加入时间:2004-11-10 |
本篇文章回答以下问题:
4.1) 要如何在使用者不必按 RETURN 的情况下从 terminal 读进东西?
4.2) 我要如何在未曾真的读进东西的情况下检查是否有字元等待读取?
4.3) 要怎样才能得知一个已open 档案之档名?
4.4) 一个执行中的程式如何知道自己的 pathname?
4.5) 如何用 popen() 对一个 process 做读写的动作?
4.6) 在 C 程式中要怎么用 sleep() 才能够 sleep 小于一秒?
4.7) 如何让 setuid 的 shell script 可以使用?
4.8) 我要如何得知有哪些 process 开了某一档案,或某一 process 正在使用哪
一个 fileystem(以至于我无法 unmount 这个 filesystem)?
4.9) 我要怎么知道是谁在 finger 我啊?
4.10) 能不能在一个 process 和 terminal 的连接已经断掉之后再接回来,例如
在 background 跑一个程式然后就 logout 而断掉的程式?
4.11) 有没有办法可以偷听一个 terminal,就是说将其输出复制一份至其他的
terminal。
4.1) 要如何在使用者不必按 RETURN 的情况下从 terminal 读进东西?
在 BSD 中用 cbreak 模式,在 SysV 中则用 ~ICANON 模式。
如果你懒得用 "ioctl(2)" 来设定 terminal 的参数,也可以用 stty 来做,
不过有点慢又没有效率就是了。底下的程式自己看著办吧:
#include <stdio.h>
main()
{
int c;
printf("Hit any character to continue\n");
/*
* ioctl() would be better here; only lazy
* programmers do it this way:
*/
system("/bin/stty cbreak"); /* or "stty raw" */
c = getchar();
system("/bin/stty -cbreak");
printf("Thank you for typing %c.\n", c);
exit(0);
}
有好几个人送给我更正确的解法。不过很抱歉我不想把它们加进去,因为这已经
超出这份文件的范围了。
通常对这个问题有兴趣的人,都是想要做一些控制萤幕显示之类的事情。如果你
也是的话,那请参考 "curses" 的相关文件。 "curses" 是一个 portable 的萤
幕控制函数库。
4.2) 我要如何在未曾真的读进东西的情况下检查是否有字元等待读取?
一些版本的 UNIX 提供了检查某个 file descriptor 目前是否有东西待读取的
方法。在 BSD 中,可以用 "select(2),也可以用 FIONREAD ioctl,检查有几
个字元等待读取,不过这只对 terminal, pipe, 与 socket 有用。在 System
V Release 3 中可以用 poll(2),不过只对 stream 有用。在 Xenix 与 Sys V
r3.2 及其以后的版本里,有一个名叫 rdchk() 的 system call 可以用来检查
对一个 file descriptor 做 read() 会不会卡住。
没有方法可以用来判断是否有字元在 FILE pointer 中待读取。(你可以直接查
看 stdio 的资料结构,看看是否 input buffer 是空的,但是这方法有时会失
效,因为你没有办法知道当你下一次要填满这个 buffer 时会发生什么事。)
有时人们问这个问题是因为想写
if (characters available from fd)
read(fd, buf, sizeof buf);
以达成 nonblocking read。这不是一种好的做法,因为可能测的时候有东西,
要读的时候,已经没有东西可读了。正确的做法应该是用 fcntl(2) 里的
F_SETFL 设定 O_NDELAY。比较旧的系统(Version 7, 4.1 BSD) 没有
O_NDELAY,那就得用 alarm(2) 来设定 read 的 timeout,以达成近似
nonblocking read 的功能。
4.3) 要怎样才能得知一个已 open 档案之档名?
这个是非常困难的。若是这个 file descriptor 是对应到 pipe 或 pty 就没
有名字了。这个 file descriptor 对应的档案也有可能已被删除。若是有
symbolic link 或 hard link,则可能有许多个名字。
如果你经过一再考虑后别无选择一定要这么做的话,可以用 find 的 -inum 与
-xdev 选项,或用 ncheck,或用自己写类似的程式来做。在这么做时要耐心的
等,因为在一个几百 megabyte 甚至几 gigabyte 的 file system中找一个档
案,一定得花不少时间。
4.4) 一个执行中的程式如何知道自己的 pathname?
若果 argv[0] 是以 "/" 开始的字,它可能就是你的程式所在地的绝对路径。
如果不是那就得照顺序检查 PATH 里的每一个目录看看里面是否有与 argv[0]
一样的程式。如果找得到的话将那个目录与程式名称兜起来可能就是你要的
pathname 了。
不过上述方法找到的并不一定是正确的,因为在程式中用到 exec() 时,
argv[0] 是可以随便给的。将 argv[0] 设为与要执行的程式名称相同只是一
种惯用法罢了!
以下的例子可能会使你更清楚些:
#include <stdio.h>
main()
{
execl("/usr/games/rogue", "vi Thesis", (char *)NULL);
}
这个被执行的程式就会认为它的名字(argv[0] 之值)是 "vi Thesis")。
4.5) 如何用 popen() 对一个 process 做读写的动作?
用 pipe 将一个 process 的输出、输入转给任意的 process 所可能会发生的
问题就是 deadlock,譬如这两个 processes 刚好同时都在等待「尚未产生」
的输入时。唯一能避免 deadlock 的方法就是在 pipe 的两端都要遵循严格的
deadlock-free 协定,但是需要这些 processes 之间的互相合作才能达成,
而对于像 popen() 这类的函数来说并不太适合。
在 'expect' 这个软体中附有一个能够让 C 程式直接引用的函式库。其中有
一个函式不管是在读或写都能达到和 popen 相同的功能。但是这个函式使
?nbsp;
用 ptys 而不是 pipes,也没有 deadlock 的问题,并且在 BSD 或 SV 中都
能使用。若想对 'expect' 有进一步的了解,可参考下一个问题的解答。
4.6) 在 C 程式中要怎么用 sleep() 才能够 sleep 小于一秒?
首先要注意的是,你只能指定 delay 的「最短」时间;实际上会 delay 多久和
系统的 scheduling 方式有关,例如系统当时有负载。如果你倒楣的话,它还可
能会 delay 蛮长的时间。
并没有一个标准函式能够在「小睡」(很短的 sleep)期间提供你计数的功能。
某些系统有提供 usleep(n) 的函式,它能够暂停执行 n 微秒(microsecond)
的时间。如果你所使用的系统没有提供 usleep() 函式,那么以下有可在 BSD,
System V 使用中的作法。
接下来的这段程式码是 Doug Gwyn 在 System V 中模拟 4BSD 并利用 4BSD
中的 select() 系统呼叫。Doung 自己都叫它为 'nap()' ;你也可以把它叫做
"usleep()";
/*
usleep -- support routine for 4.2BSD system call emulations
last edit: 29-Oct-1984 D A Gwyn
*/
extern int select();
int
usleep( usec ) /* returns 0 if ok, else -1 */
long usec; /* delay in microseconds */
{
static struct /* `timeval' */
{
long tv_sec; /* seconds */
long tv_usec; /* microsecs */
} delay; /* _select() timeout */
delay.tv_sec = usec / 1000000L;
delay.tv_usec = usec % 1000000L;
return select( 0, (long *)0, (long *)0, (long *)0, &delay );
}
On System V you might do it this way:
/*
subseconds sleeps for System V - or anything that has poll()
Don Libes, 4/1/1991
The BSD analog to this function is defined in terms of
microseconds while poll() is defined in terms of milliseconds.
For compatibility, this function provides accuracy "over the long
run" by truncating actual requests to milliseconds and
accumulating microseconds across calls with the idea that you are
probably calling it in a tight loop, and that over the long run,
the error will even out.
If you aren't calling it in a tight loop, then you almost
certainly aren't making microsecond-resolution requests anyway,
in which case you don't care about microseconds. And if you did,
you wouldn't be using UNIX anyway because random system
indigestion (i.e., scheduling) can make mincemeat out of any
timing code.
Returns 0 if successful timeout, -1 if unsuccessful.
*/
#include <poll.h>
int
usleep(usec)
unsigned int usec; /* microseconds */
{
static subtotal = 0; /* microseconds */
int msec; /* milliseconds */
/* 'foo' is only here because some versions of 5.3 have
* a bug where the first argument to poll() is checked
* for a valid memory address even if the second argument is 0.
*/
struct pollfd foo;
subtotal += usec;
/* if less then 1 msec request, do nothing but remember it */
if (subtotal < 1000) return(0);
msec = subtotal/1000;
subtotal = subtotal%1000;
return poll(&foo,(unsigned long)0,msec);
}
在 System V 或其他 非-BSD 的 Unix 中要使用这类的「小睡」程式,可以用
Jon Zeeff 的 s5nap,它曾被发表在 comp.sources.misc, volume 4 中。它
需要安装一个驱动程式,但是装好后就可以跑得很好。(它的精确度会受到
kernel 中 HZ 这个变数的影响,因为它是用到了 kernel 中的 delay() 函
式。)
现在很多较新版本的 Unix 都有提供这类的「小睡」功能了。
4.7) 如何让 setuid 的 shell script 可以使用?
[ 这个问题的回答很长,但是这是一个复杂又常问的问题。在此要谢谢 Maarten
Litmaath 所提供的答案和以下所提到的 "indir" 程式。]
先假设你所用的 UNIX 是能认得「可执行的 shell script」的变异过的 UNIX
(如 4.3BSD 或 SunOS)。这类 script 的第一行一定是如以下一般:
#!/bin/sh
这样的 script 就是所谓可执行的 script,因为它和一般可执行的binary 档
一样有 magic number 做开头。在我们所用的例子中,magic number 为
'#!',OS 会把这行接下来的部份当作这整个 script 的解译程式,其后可能还
会有一些 option 如:
#!/bin/sed -f
假设这个 script 的名字叫做 'foo',并且放在 /bin 下,那么如果你用:
foo arg1 arg2 arg3
那么 OS 实际在执行时会把它看成是:
/bin/sed -f foo arg1 arg2 arg3
有一点不同的是:如果 'foo' 被设定成 setuid,那么 OS 会把它以第一种格
式来解释;如果你硬是以第二种格式输入,那么 OS 会以 /bin/sed 的
permission 为准,而它当然不会是 setuid。
好吧,那如果我的 shell script 并不是以 '#!' 做开头,或是我的 OS 根本就
不认得它呢?
嗯,如果这个 shell(或是其他的解译程式)试著要去执行它,那么 OS 会传回
一个错误讯息,表示这个档案不是以合法的 magic number 做开头。收到这个错
误讯息后,shell 会把这个档案认定成是 shell script,并以另一种方式来执行:
/bin/sh shell_script arguments
但是我们在前面已经看到了,在这样的情形下,被设成为 setuid 的
shell_script 并不会发生作用。
那么,设成 setuid 的 shell script 到底有什么安全上的问题呢?
嗯,假设这个 script 叫做 '/etc/shell_script',它的开头是:
#!/bin/sh
现在我们来看看以下的命令会发生什么事:
$ cd /tmp
$ ln /etc/setuid_script -i
$ PATH=.
$ -i
我们可以看出来,以上的最后一个命令会被解释成:
#!/bin/sh -i
而这样的命令会让我们得到一个可以输入命令的 shell,并且会被 setuid 成
这个 script 的拥有者。
幸好,这样的安全漏洞可以很轻易地防止,只需要把第一行改成:
#!/bin/sh -
'-' 这个符号代表著它是整个 option list 的结尾:所以如果再用前述的方法
的话,'-i' 就会如本来所期望的被解释成 script 档案的名字。
然而,还有更严重的问题:
$ cd /tmp
$ ln /etc/setuid_script temp
$ nice -20 temp &
$ mv my_script temp
第三个命令会被解释成:
nice -20 /bin/sh - temp
而因为这个命令的优先权被设得很低,那么第四个命令可能就有机会抢先在
shell 开启 'temp' 之前就用 'my_script' 把 'temp' 给盖掉!有四种方法
可以修补这个安全上的漏洞:
1) 让 OS 用另一个比较安全的方式执行 setuid script。如 System V R4 和
4.4BSD 利用 /dev/fd 来把该 script 的 file descriptor 传给解译程式。
2) 透过一个前端程式来间接解译要执行的 script,以确定在真正的解译程式
启动前一切正常。例如,你可以用 comp.sources.unix 中的 'indir' 程
式,那么你的 script 开头就会像这样:
#!/bin/indir -u
#?/bin/sh /etc/setuid_script
3) 造一个 'binary wrapper':一个真正的 setuid 可执行程式,这个程式的
唯一功能就是用来执行 script 中所指定的解译程式,并以该 script 的档
名为参数传给解译程式。
4) 造一个 'setuid script server' ,并把一些要用到、检查过的 setuid
script 存放在 database 中。当成功地被呼叫后,会去执行正确的解译程
式及正确的 script。
现在我们已经能确定所解译到的 script 是正确的,那么还有其他的危险吗?
很抱歉,当然还有!在使用 shell scipt 的时候,你一定不能忘记要把 PATH
这个变数很明确地设到正确的路径去。你能够指出这是为什么吗?除此之外,
还有 IFS 这个变数如果没设好也可能会造成问题。其他的环境变数也可能会形
成安全上的问题,如 SHELL... 更重要的,你必须要确定在 script 中没有命
令会让它产生出可下命令的 shell(interactive shell escape)!还有就是,
umask 可能被设成奇怪的值等等...
除此之外,你应该要知道 setuid script 会「继承」所有它所用到的命令的
bug 及安全问题。
总而言之,你应该知道 setuid shell script 真的是件非常危险的事吧!
最好还是写 C 程式啦。
4.8) 我要如何得知有哪些 process 开了某一档案,或某一 process 正在使用哪
一个 fileystem(以至于我无法 unmount 这个 filesystem)?
你可以用 fuser(system V),fstat(BSD),ofiles(public domain) 或是 pff
(public domain)。这些程式可以告诉你哪些 processes 正在使用哪些档案。
4.3BSD 的 fstat,有一份 Dynix,SunOS 与 Ultrix 都可以用的 port。你可以
找找放 comp.sources.unix, volume 18的地方。
Pff 是 kstuff 这套软体的一部分,很多系统上都可以用。欲取得 kstuff 请参
考问题 3.10。
4.9) 我要怎么知道是谁在 finger 我啊?
一般来说,你是无法找出在远端机器 finger 你的那个人的 userid 的。你大概
只能找出在从哪台机器 finger 的。另外有一种可行的方法,如果你的系统支援
并且假设 finger daemon 不反对的话,那么可以把你的 .plan 档用 "named
pipe" 而不用一般的文字档。(用 'mknod' 来造)
接下来,你执行一个程式去写(open for writing)你的 .plan 档;但是由于
你的 .plan 是一个 "named pipe",所以这个开档的动作要一直等到有其他的
process 去读(open for reading)你的 .plan 档时才会成功。现在你就可以
任意地把你所想让人 finger 到的 .plan 内容写入这个 pipe。在
comp.sources.misc, volumn 41 中有个 "planner" 的程式可以做这件事。
当然,如果你的系统不支援 "named pipe",或是你所用的 finger 程式只接受
纯文字的 .plan 档,那么以上的方法就行不通了。
你的程式也可以藉由查看 "netstat" 的输出,来找出这次的 finger 是从那里
连过来的,但是这并无法看出远端执行 finger 的人是谁。
想要知道远端的人是谁,必须要远端的机器有跑支援如 RFC 931 的识别程式才
行。
现在,在常见的 BSD 系统上就有三种 RFC 931 的实做程式,同时也有许多的
应用程式(如 wuarchive ftpd)支援。如果你想得到更多有关 RFC 931 的资
讯,可以加入 rfc931-user 的 mailing list,
rfc931-users-request@kramden.acf.nyu.edu。
另外还有三个注意事项。第一,有许多的 NFS 系统无法正确地处理 "named
pipe"。这个意思是,当你要去读放在另一部机器上的 pipe 时,可能会被系统
block 住直到time out,或是因为 pipe 的档案长度是 0 所以系统就不把它的
内容列出来。
第二,在许多的系统中,fingerd 会在读 .plan 档之前先去检查它里面是否真
的有资料并且是可以读的。这样就会造成远端 finger 你的人根本就看不到你
的 .plan 档,因为你的 .plan 长度是 0。
第三,支援 "named pipe" 的系统通常在同一个时间里只允许系统中存在某个固
定数目的 pipes,检查 kernel 的 config 档和 FIFOCNT 选项即可得知。如果
系统中的 pipe 数目超过了 FIFOCNT,那么系统就会暂停所有新的 pipe 直到有
人关掉他先前?nbsp;pipe。这是因为 pipe 所用到的 buffer 是放在
non-paged 的记忆体中。
4.10) 能不能在一个 process 和 terminal 的连接已经断掉之后再接回来,例如
在 background 跑一个程式然后就 logout 而断掉的程式?
大部份版本的 Unix 都不像 VMS 和 Multics 等作业系统支援 "detaching" 和
"attaching" process。不过,有两个免费的软体能够帮你达成这个目的?nbsp;
?nbsp;
第一个是 "screen",在 comp.sources.unix 中的标题是 "Screen, multiple
windows on a CRT"。(你可以在 comp.sources.misc, volumn 28 中发表的
"screen-3.2" 中找到)这个软体在 BSD,System V r3.2 及 SCO UNIX 皆可执
行。
第二个是 "pty",在 comp.sources.unix 中的标题是 "Run a program under
a pty session"。(可以在 volumn 23 发表的 "pty" 中找到)"pty" 只能在
BSD 的系统中执行。
以上这两个软体都有溯及既往的能力,也就是说,你如果想对某个 process
做 detach 或 attach 的动作,那么就必须要先在 screen 或 pty 下启动它才
行。
4.11) 有没有办法可以偷听一个 terminal,就是说将其输出复制一份至其他的
terminal。
有几种方法可以达成这个目的,不过没有一个是完美的:
* kibitz 允许两个(或更多)的人透过 shell(或其他的程式)彼此沟通。
它的用途有:
- 监视或援助令一人的terminal session;
- 记录所有的输出入以用来能卷页回去,储存整个输出入,甚至是可
以直接编辑它。
- 当团队合作时,如制作 game、撰写文件等工作,每个人都各有所长
各有所短,就可用此种方式彼此互补。
kibitz 是 'expect' 软体的一部份,请查看问题 3.9。
kibitz 需要有被监视人的同意才能进行。如果想不取得同意就进行监视,那么就
得用一些比较令人讨厌的方法了:
* 你可以自己写一个程式去搜查整个 kernel 的结构,监视 terminal 所用的
output buffer,然后把它所输出的字全抓下来。很明显的,这是熟悉 Unix
kernel 的人才可能做得到的。但是,不论你是用什么方法大概都无法
拿到其他的 Unix 去用。
* 如果你是想要随时监视一个特定、实际拉线连上的 terminal(例如,你想让
管理者能够从其他机器的终端机查看某部机器的 console),那你可以乾脆
接一台监视器在通往那台 terminal 的线上。举例来说,把你的监视器的输
出接到另一台机器的 serial port,然后执行一个程式去收集那个 port 的
输入并把它转到另一个 port 去,而这个 port 就真的连到你所要监视的
terminal 去。这样做的话,你必须确定从该 terminal 来的输出会随著线传
回去,即使你只是插接在电脑和 terminal 的中间,这并不会太难做到。 用
这个方法,对 terminal 布线不太熟悉的人是无法发觉的。
* 在最近一版的 screen 中有一种 multi-user 模式可以用。若想多了解一
点 screen,请看问题 4.10。
* 如果你所使用的系统有提供 stream(如 SunOS,SVR4)那么你可以使用
发表在 comp.sources.misc, volumn 28 中的 advise 程式。它并不需要先
执行。(但是,你必须要事先把你的系统设定成在开启 tty 或 pty 时会自
动把 advise 模组放入 stream 中。)
[文章录入员:tonny] |
|
|
|
|