FAQ汇萃
>> solaris 专栏
>> 钱飞老师的solaris技术问答(5)-NFS暗号信息的解析方法
由 fei 发布于: 2001-03-06 13:55
问:在我们的局部网中使用着NFS,NFS的服务器由运行Solaris 2.X系统的SUN工作 站来担当。但最近频繁地出现以下信息。
SunOS 4.1.x信息<br>
NFS write error 13 on host A fh 1cc0006 2 a0000 6db28 0 a0000 2 0
Solaris 2.x信息<br>
NFS write error on host A Permission denied.<br>
File: userid=0, groupid=0<br>
(file handle: 1cc0006 2 a0000 6db28 0 a0000 2 0)
仅仅依靠这些信息我们还无法断定出错原因,需要对信息加以解析以便定位。 在全部由SunOS 4.1.x系统所构成的网络中,NFS服务器上事先启动rpc.showfhd 常驻进程,当出现类似错误信息时,可以在NFS客户机上执行showfh命令,从错 误信息调查相应的文件名。但在Solaris 2.X上没有这类命令,有没有办法可以 进行类似调查?
答:showfh命令的存在很少为人所知。用该命令的确可以从NFS error信息来调查N FS服务器上的出错文件名。showfh命令对于 NFS纠错和性能调节来说有着相当 重要的作用。但在Solaris 2.X上没有这类命令的确让人感到遗憾(据SUN称没 有相应的替换命令)。光遗憾亦与事无补,让我们自己来挑战一下这个问题吧。
1. 来自NFS的暗号文
众所周知,在UNIX系统上存取文件或执行命令时,需要用到UNIX PATH的概念。即便是对NFS文件系统所装载的远程文件系统,用户也不需要做特殊的考虑,仍可以用 UNIX PATH来进行处理。
但在NFS内部则完全不是这么简单。NFS具有很强的柔软性,在设计上并不仅仅针对UNIX系统,NFS服务器可以不是UNIX计算机。正是由于考虑到了这一点,在NFS的内部才不采用UNIX PATH。为了表示特定的文件,NFS内部采用了FILE HANDLE 的概念(以下简称为fh)。
在NFS error信息中包有含出错文件信息。但由于NFS error为内部错误,因此这种信息是以fh的形式来表示的。然而,fh信息对于习惯了UNIX PATH的用户来说跟暗号文一样,很难理解。例如,在上述问题中NFS error信息中便含有以下fh值:
1cc0006 2 a0000 6db28 0 a0000 2 0
2. 知彼求策
fh信息的这些数字到底表示了什么呢?
首先我们可以看到该信息有8个字段所构成。但这8个字段意味着什么,笔者也曾查阅了许多资料和手册仍不知所云。估计这可能与fh之本身性质有关。
其实,fh是只有NFS服务器才能理解的一种暗号。NFS客户机在调用NFS服务器上的文件时,首先从NFS服务器接收fh,然后在读写该文件时用fh来定位NFS服务器上的该文件之所在。因此,在NFS客户机上并不需要理解所收到的fh之内容。
另外,如上所述,NFS服务器不一定非UNIX计算机不可。如果NFS服务器为SUN工作站的话,系统在对文件进行定位时可以利用inode号码,但在非UNIX计算机上可能根本就不存在inode的概念。因此可知,完全没有必要决定fh的信息格式。只要有一个大致的标准,按照该标准NFS服务器只要能够按照自己易懂的方法决定fh的内容即可。
NFS 服务器根据版本不同存在着差异。不确定的要素太多也许正是无法文件化的原因所在。
您的问题是针对SUN工作站而言的,因此,我们需要花一些力气来理解一下SUN工作站上的NFS服务器的fh内容。
3. 揭开谜底
这里需要一些有关C语言和SunOS的基本知识。为了追求线索,让我们先用最笨,但也可能是最简单的方法来查看一下SUN的fh含义。
首先,逐个的查看一下与NFS有关的C程序HEAD文件。
% view /usr/include/nfs/*
工夫不负有心人,与fh格式相关连的基本定义好象是由/usr/include/nfs/nfs.h中的下述模块所给出。
#define NFS_FHSIZE 32
#define NFS_FHMAXDATA ((NFS_FHSIZE - sizeof(struct fhsize) + 8)/2)
struct svcfh {
fsid_t fh_fsid; /* filesystem id */
u_short fh_len; /* file number length */
char fh_data[NFS_FHMACDATA]; /* and data */
u_short fh_xlen; /* export file number length */
char fh_xdata[NFS_FHMACDATA];/* and data */
};
但是,光靠这点儿信息还远远不够,还需要对NFS服务器与客户机之间的具体信息交换来进行调查,从网络上截取NFS信息包。
在Solaris上可以用snoop命令来观察网络状况。
% snoop -v rpc nfs
NFS: ------------------ entry #7
NFS: File ID = 149780
NFS: Name = index.htm
NFS: Cookie = 164
NFS: Post-operation attributes:
NFS: File type = 1 (Regular File)
NFS: Mode = 0644
NFS: Setuid = 0, Setgid = 0, Sticky = 0
NFS: Owner's permissions = rw-
NFS: Group's permissions = r--
NFS: Other's permissions = r--
NFS: Link count = 1, User ID = 0, Group ID = 1
NFS: File size = 5465, Used = 98304
NFS: Special: Major = 0, Minor = 0
NFS: File system id = 8388620, File id = 149780
NFS: Last access time = 20-Oct-96 23:13:00.420002000 GMT
NFS: Modification time = 16-Jul-96 03:23:41.569999000 GMT
NFS: Attribute change time = 16-Jul-96 03:23:41.569999000 GMT
NFS:
NFS: File handle = 0080000C00000002000A000000024914
NFS: 62DA8A7E000A00000001EF07788C7F8A
从snoop命令的执行结果中我们可以得到许多提示。首先,我们已经知道,当出现 NFS error时,fh信息曾被分割成8个字段。因此,让我们将上述结果中fh信息值分成8个等份,可见,
File handle = 0080000C 00000002 000A0000 00024914
62DA8A7E 000A0000 0001EF07 788C7F8A
在UNIX系统上,本地硬盘上的文件是由inode来管理的。inode在文件系统中具有一个唯一的号码。同样文件系统也具有一个类似的唯一号码,称之为文件系统号码。因此,只要能够定位文件系统及inode号码,即可定位文件名。在上述结果中,文件系统id(File system id)及inode号码(File id)分别为:
File system id = 8388620, File id = 149780
由于这里的“File system id”和“File id”均为10进制表示,为进行比较,先将其转换为16进制表示。
% bc
obase=16
8388620 <---- File system id的10进制表示
80000C
149780 <---- File id的10进制表示
24914
与上述结果相比较可以发现,"File system id"为fh的第一字段值,"File id"为第四字段值!
4. 新命令诞生
我们最终的目标是定位出错文件,仅仅知道了上述"File system id"和"File id" 还不行,还必须确定相应的UNIX PATH。
在Solaris上“File system id”可以从/etc/mnttab文件中得到。用程序读取该文件时,可以使用getmntent(3C)函数。/etc/mnttab文件内含有以下定义:
/dev/dsk/c0t1d0s4 /mnt ufs suid,rw,dev=80000c 852450814
这里,dev=XXX处定义值即为“File system id”的16进制表示。 通过比较"File system id"和"File id"便可定位文件名。具体的定位程序如下。
------------------------------------------------------------------------
/* findfh.c
* 从NFS的fh (File Handle) 定位UNIX PATH。
* 执行时请在命令行上指定NFS error信息中的NFS fh值(8字段16进制) 。
*/
#include <stdio.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/statvfs.h>
#include <sys/mnttab.h>
#include <sys/mntent.h>
#include <sys/stat.h>
#include <sys/fs/ufs_fs.h>
char *find_inode(char *, int) ;
main(int argc, char **argv)
{
int ret ;
char *fn ;
FILE *fp ;
u_long ino ;
u_long fsid ;
struct mnttab mnt ;
struct statvfs vfs ;
if(argc != 9) {
fprintf(stderr, "Usage: %s nfs_fh\n", argv[0]) ;
exit(1) ;
}
/* 抽取NFS filehandle中的filesystem ID */
if(sscanf(argv[1], "%x", &fsid) != 1) {
fprintf(stderr, "illegal parameters: arg#1\n") ;
exit(1) ;
}
/* 抽取NFS filehandle中的 inode号码 */
if(sscanf(argv[4], "%x", &ino) != 1) {
fprintf(stderr, "illegal parameters: arg#4\n") ;
exit(1) ;
}
/* 打开/etc/mnttab文件 */
if((fp = fopen(MNTTAB, "r")) == NULL) {
perror(MNTTAB) ;
exit(1) ;
}
/* 从mnttab 抽取当前mount着的filesystem名 */
while(! (ret = getmntent(fp, &mnt))) {
/*
* 忽略MNT_TOOLONG, MNT_TOOMANY, MNT_TOOFEW
*/
if(ret > 0)
continue ;
/* 忽略UFS mount以外的filesystem */
if(strcmp(mnt.mnt_fstype, MNTTYPE_UFS))
continue ;
/* 抽取filesystem信息 */
if(statvfs(mnt.mnt_mountp, &vfs) < 0) {
perror(mnt.mnt_mountp) ;
continue ;
}
/* 检查filesystem id是否一致 */
if(fsid == vfs.f_fsid)
break ;
}
fclose(fp) ;
/* 查看有无相应的 filesystem */
if(ret < 0) {
fprintf(stderr, "Could not get mount point\n") ;
exit(1) ;
}
/* 将工作点移动到filesystem的mount位置 */
if(chdir(mnt.mnt_mountp) < 0) {
perror(mnt.mnt_mountp) ;
exit(1) ;
}
/* 查找具有相同inode的文件 */
if((fn = find_inode(".", ino)) == NULL) {
fprintf(stderr, "no file with this filehandle\n") ;
exit(1) ;
}
else {
printf("\nUNIX PATH: %s\n", fn) ;
}
exit(0) ;
}
/*
* 递归检索filesystem树。发现相同的inode时返回。
*/
char *
find_inode(char *name, int ino)
{
DIR *dp ;
char *str ;
char cwd[MAXPATHLEN] ;
struct stat stat ;
struct dirent *de ;
static char buf[MAXPATHLEN] ;
# define PARENT (cwd[0] == '\0' ? "(null)" : cwd)
/* 保存当前工作点*/
if((getcwd(cwd, MAXPATHLEN)) == NULL)
perror("getcwd") ;
/*
* 由于需要识别链接文件,因此不能使用stat()函数,而需要使用
* lstat()函数。
*/
if(lstat(name, &stat) < 0) {
sprintf(buf, "%s/%s", PARENT, name) ;
perror(buf) ;
return(NULL) ;
}
/* 查看inode号码是否一致 */
if(stat.st_ino == ino) {
/* 一致,返回文件名 */
sprintf(buf, "%s/%s", PARENT, name) ;
return(buf) ;
}
/* 查看是否为目录 */
if(! S_ISDIR(stat.st_mode))
return(NULL) ;
/* 移动到目录下 */
if(chdir(name) < 0) {
sprintf(buf, "%s/%s", PARENT, name) ;
perror(buf) ;
return(NULL) ;
}
/* 打开目录 */
if((dp = opendir(".")) == NULL) {
sprintf(buf, "%s/%s", PARENT, name) ;
perror(buf) ;
if(chdir("..") < 0) {
sprintf(buf, "%s/%s/..", PARENT, name) ;
perror(buf) ;
}
return(NULL) ;
}
/*
* 递归检索当前目录,忽略"."和".."文件
*/
while((de = readdir(dp)) != NULL) {
if((! strcmp(de->d_name, ".")) ||
(! strcmp(de->d_name, "..")))
continue ;
if((str = find_inode(de->d_name, ino)) != NULL) {
closedir(dp) ;
return(str) ;
}
}
/*
* 当前目录检索完毕,返回上层目录。
*/
if(chdir("..") < 0) {
sprintf(buf, "%s/%s/..", PARENT, name) ;
perror(buf) ;
}
closedir(dp) ;
return(NULL) ;
}
5. 执行
% cc -o findfh findfh.c
% findfh 0080000C 00000002 000A0000 00024914
62DA8A7E 000A0000 0001EF07 788C7F8A
UNIX PATH: /mnt/WWW/httpd/htdocs/index.htm
(钱飞/fei@come.or.jp)
--------------------------------------------------------------------------------
|