英语法语西班牙语

Ad


OnWorks 网站图标

解释_lca2010 - 云端在线

通过 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器在 OnWorks 免费托管服务提供商中运行 Explain_lca2010

这是命令 Explain_lca2010,可以使用我们的多个免费在线工作站之一在 OnWorks 免费托管服务提供商中运行,例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器

程序:

您的姓名


解释_lca2010 - 找不到媒体:什么时候停止尝试阅读 冲突(3) 的
心神。

动机


早在 1980 年代初,我就想到了 libexplain。 每当系统调用
返回一个错误,内核确切地知道出了什么问题......并将其压缩为
少于 8 位 埃尔诺. 用户空间可以访问与内核相同的数据,它
用户空间应该可以准确找出引发错误的原因
返回,并使用它来编写好的错误消息。

会这么简单吗?

误差 条未读消息 as 灵巧
好的错误消息通常是那些按计划被丢弃的“百分之一”的任务
压力会挤压你的项目。 然而,一个好的错误信息可以使一个巨大的,
当用户陷入恐慌时,对用户体验的不成比例的改善
通常不会遇到的未知领域。 这不是一件容易的事。

作为一个幼虫程序员,作者没有看到(完全准确)错误的问题
像这样的消息:
浮动异常(核心转储)
直到指出替代的非程序员解释。 但那不是
Unix 错误消息唯一的错误。 您多久看到一次错误消息,例如:
$ 。/愚蠢的
无法打开文件
$
此时,开发人员有两种选择:

1.
您可以运行调试器,例如 GDB(1),或

2.
您可以使用 痕迹(1)或 桁架(1) 往里面看。

· 请记住,您的用户甚至可能无法访问这些工具,更不用说能力了
使用它们。 (这是很长一段时间以来 Unix的 初学者 意思是“只写了 一种
设备驱动”。)

然而,在这个例子中,使用 痕迹(1) 揭示
$ 痕迹 -e 跟踪=打开 。/愚蠢的
open("some/file", O_RDONLY) = -1 ENOENT(没有那个文件或目录)
无法打开文件
$
这比错误消息提供的信息要多得多。 通常情况下,
愚蠢的源代码看起来像这样
int fd = open("某物", O_RDONLY);
如果 (fd < 0)
{
fprintf(stderr, "无法打开文件\n");
出口(1);
}
用户没有被告知 文件,也没有告诉用户 错误。 是文件
即使在那里? 是否存在权限问题? 它确实告诉你它正试图打开一个
文件,但这可能是偶然的。

拿起你的线索棒,用它打败幼虫程序员。 告诉他 错误(3)。
下次使用该程序时,您会看到不同的错误消息:
$ 。/愚蠢的
打开:没有那个文件或目录
$
进步,但不是我们所期望的。 如果出现错误信息,用户如何解决问题
没有告诉他问题是什么? 查看源代码,我们看到
int fd = open("某物", O_RDONLY);
如果 (fd < 0)
{
perror("打开");
出口(1);
}
是时候用线索棒再次运行了。 这一次,错误信息走一步
前进后退一步:
$ 。/愚蠢的
某物: 没有这样的文件或目录
$
现在我们知道它试图打开的文件,但不再被告知它是 打开(2)
那失败了。 在这种情况下,它可能并不重要,但对于以下情况可能很重要
其他系统调用。 本来可以 创造(2) 相反,一个操作意味着
需要不同的权限。
const char *文件名 = "某物";
int fd = 打开(文件名,O_RDONLY);
如果 (fd < 0)
{
错误(文件名);
出口(1);
}
不幸的是,上面的示例代码也是非幼虫程序员的典型代码。 时间
告诉我们的学徒 冲突(3)系统调用。
$ 。/愚蠢的
打开 某物: 没有这样的文件或目录
$
这最大化了可以呈现给用户的信息。 代码看起来像
这个:
const char *文件名 = "某物";
int fd = 打开(文件名,O_RDONLY);
如果 (fd < 0)
{
fprintf(stderr, "打开 %s: %s\n", 文件名, strerror(errno));
出口(1);
}
现在我们有了系统调用、文件名和错误字符串。 这包含所有
信息 痕迹(1) 印刷。 那就好办了。

还是呢?

限制 of 错误 冲突
作者在 1980 年代看到的问题是错误消息不完整。
“没有这样的文件或目录”是指“一些”目录,或到“” 文件在
一些“ 目录?

快速浏览一下手册页 冲突(3) 讲述:
strerror - 返回描述错误号的字符串
请注意:它描述了错误 ,不是错误。

另一方面,内核 知道 错误是什么。 有一个特定的点
内核代码,由特定条件引起,内核代码分支并说“不”。
用户空间程序能否找出特定条件并编写更好的错误
信息?

然而,问题更深了。 如果在使用过程中出现问题怎么办 (2)系统
调用,而不是 打开(2) 打电话? 与相关联的错误消息很简单
打开(2) 包含文件名,它就在那里。 但是为了能够包含文件名
在与错误相关的 (2) 系统调用,你必须把文件名全部传过去
调用堆栈的路径,以及文件描述符。

这是令人讨厌的一点:内核已经知道文件的文件名
描述符相关联。 为什么程序员必须将冗余数据全部传递
向下调用堆栈的方式只是为了改进可能永远不会发出的错误消息? 在
现实中,很多程序员都懒得理会,由此产生的错误信息对于
它。

但那是 1980 年代,在 PDP11 上,资源有限且没有共享库。 后退
然后,不包括 Unix 的味道 / proc中 即使是基本的形式,而且 (1) 程序
十多年了。 所以这个想法被搁置了,因为不切实际。

Level 无限 支持
想象一下,你是水平无限支持。 你的工作描述说你从来没有
曾经 必须与用户交谈。 那么,为什么仍然有源源不断的人想要
您,本地 Unix 大师,要破译另一个错误消息吗?

奇怪的是,25 年后,尽管有一个简单的权限系统,实现了完整的
一致性,大多数Unix用户仍然不知道如何解码“没有这样的文件或目录”,
或他们每天看到的任何其他神秘错误消息。 或者,至少,对
他们。

如果一级技术支持不需要解密错误消息,那不是很好吗?
拥有用户无需调用即可理解的错误消息不是很好吗
技术支持?

这些日子 / proc中 在 Linux 上不仅能够提供解码所需的信息
绝大多数错误消息,并指出用户的近因
问题。 在有限的系统上 / proc中 实施, (1) 命令可以填写
许多差距。

2008 年,翻译请求源源不断地发生在作者身上。 它是
是时候重新审视这个 25 年前的想法了,libexplain 就是结果。

使用 图书馆


在可能的情况下,库的接口尽量保持一致。 让我们从一个
使用示例 冲突(3):
如果(重命名(旧路径,新路径)<0)
{
fprintf(stderr, "重命名 %s %s: %s\n", old_path, new_path,
错误(错误号));
出口(1);
}
libexplain 背后的想法是提供一个 冲突(3) 相当于 系统调用,
专门针对该系统调用量身定制,以便它可以提供更详细的错误信息
消息,包含您在部分“错误”标题下看到的大部分信息
2 3和 男子 页,辅以有关实际情况、实参的信息
值和系统限制。

简易 案例
冲突(3)更换:
如果(重命名(旧路径,新路径)<0)
{
fprintf(stderr, "%s\n", Explain_rename(old_path, new_path));
出口(1);
}

错误 案例
也可以通过显式 埃尔诺(3) value,如果你必须先做一些
会干扰的处理 埃尔诺,例如错误恢复:
如果(重命名(旧路径,新路径<0))
{
int old_errno = 错误号;
... 打扰 埃尔诺...
fprintf(stderr, "%s\n",explain_errno_rename(old_errno,
旧路径,新路径));
出口(1);
}

多线程 案例
一些应用程序是多线程的,因此无法共享 libexplain 的内部
缓冲。 您可以使用提供自己的缓冲区
如果(取消链接(路径名))
{
字符消息[3000];
解释消息取消链接(消息, 大小(消息),路径名);
错误对话框(信息);
return -1;
}
为了完整起见,两者 埃尔诺(3) 和线程安全:
ssize_t nbytes = 读取(fd,数据,sizeof(数据));
如果(nbytes < 0)
{
字符消息[3000];
int old_errno = 错误号;
...错误 恢复...
解释消息错误错误读取(消息, 大小(信息),
old_errno, fd, 数据, sizeof(data));
错误对话框(信息);
return -1;
}

这些是替代品 错误_r(3),在拥有它的系统上。

接口
作为便利功能添加的一组功能,以吸引程序员使用
libexplain 库,原来是作者最常用的 libexplain 函数
命令行程序:
int fd = Explain_creat_or_die(文件名,0666);
此函数尝试创建一个新文件。 如果不能,它会打印一条错误消息并
以 EXIT_FAILURE 退出。 如果没有错误,则返回新的文件描述符。

相关函数:
int fd = Explain_creat_on_error(文件名,0666);
将在失败时打印错误消息,但也会返回原始错误结果,并且
埃尔诺(3) 也不受干扰。

所有类型 other 系统 电话
一般来说,每个系统调用都有自己的包含文件
#包括姓名.h>
定义了六个函数的函数原型:

· 解释_姓名,

· 解释_错误_姓名,

· 解释_消息_姓名,

·explain_message_errno_姓名,

· 解释_姓名_or_die 和

· 解释_姓名_on_error。

每个函数原型都有 Doxygen 文档,这个文档 is 不能 剥离
安装包含文件时。

等待(2) 系统调用(和朋友)有一些额外的变体也解释失败
成为不是 EXIT_SUCCESS 的退出状态。 这适用于 系统(3)和 关闭(3)as
井。

覆盖范围包括 221 个系统调用和 547 个 ioctl 请求。 还有很多系统
呼吁尚未实施。 永不返回的系统调用,例如 出口(2)、不存在
在图书馆里,永远不会。 这 EXEC 系统调用家族 ,那恭喜你, 支持,因为
它们在出现错误时返回。


这就是假设的“猫”程序的样子,带有完整的错误报告,
使用 libexplain。
#包括
#包括
#包括
libexplain 有一个包括,加上通常的嫌疑人。 (如果你想减少
预处理器负载,您可以使用特定的姓名.h> 包括。)
静态空隙
进程(文件*fp)
{
为(;;)
{
字符缓冲区[4096];
size_t n = Explain_fread_or_die(buffer, 1, sizeof(buffer), fp);
如果(!n)
打破;
解释_fwrite_or_die(缓冲区,1,n,标准输出);
}
}
过程 函数将文件流复制到标准输出。 如果发生错误
无论是读还是写,都会报告(路径名将包含在
错误)并且命令以 EXIT_FAILURE 退出。 我们甚至不担心跟踪
路径名,或将它们向下传递到调用堆栈。
INT
主要(int argc,char **argv)
{
为(;;)
{
int c = getopt(argc, argv, "o:");
如果(c == EOF)
打破;
开关 (c)
{
案例“o”:
解释_freopen_or_die(optarg,“w”,标准输出);
打破;
这段代码有趣的地方在于 libexplain 可以报错 如: 路径名 甚至
如果你 像这里所做的那样显式地重新打开标准输出。 我们甚至不担心
跟踪文件名。
默认情况下:
fprintf(stderr, "用法:%ss [ -o ] ...\n",
argv[0]);
返回 EXIT_FAILURE;
}
}
如果(选择 == argc)
进程(标准输入);
其他
{
而 (optind < argc)
{
文件*fp =解释_fopen_or_die(argv[optind]++,“r”);
进程(fp);
解释_fclose_or_die(fp);
}
}
标准输出将被隐式关闭,但为时已晚,无法报告错误
发出,所以我们在这里这样做,以防万一缓冲的 I/O 还没有写任何东西,并且
有一个 ENOSPC 错误或什么的。
解释_fflush_or_die(标准输出);
返回退出成功;
}
就这样。 完整的错误报告,清晰的代码。

生锈的 鳞片 of 接口 培育
对于那些不熟悉它的人,Rusty Russel 的“我如何使它难以误用?”
页面是 API 设计者的必读之物。
http://ozlabs.org/~rusty/index.cgi/tech/2008‐03‐30.html

10. 这是 不可能 得到 错误。

目标需要定得高,雄心勃勃,以免你完成它们并认为你是
当你不在时完成。

libexplain 库检测假指针和许多其他假系统调用参数,
并且通常即使在最艰难的情况下也会尝试避免段错误。

libexplain 库被设计为线程安全的。 可能会有更多的实际使用
揭示可以改进的地方。

最大的问题是实际的函数名称本身。 因为 C 没有
命名空间,libexplain 库总是使用一个explain_ 名称前缀。 这是
创建伪名称空间以避免符号冲突的传统方法。
然而,这会导致一些听起来不自然的名字。

9. 编译 or 链接 不会 得到 it 错误。

一个常见的错误是在explain_open_or_die 预期的地方使用explain_open。
幸运的是,此时编译器经常会发出类型错误(例如 不能分配
const char * 右值转换为 int 左值)。

8. 编译 警告 if 得到 it 错误。

如果在使用explain_rename_or_die 时使用explain_rename,这可能会导致其他
问题。 GCC 有一个有用的 warn_unused_result 函数属性,以及 libexplain
库将它附加到所有的解释_姓名 函数调用以产生警告时
犯这个错误。 结合这个 GCC -错误 将其提升到 9 级善良。

7. 明显 使用 is (大概) 正确 一。

已选择函数名称来表达它们的含义,但这并不总是
成功的。 同时解释_姓名_或_死并解释_姓名_on_error 具有相当的描述性,
较少使用的线程安全变体更难解码。 函数原型有助于
编译器理解,头文件中的 Doxygen 注释帮助用户
走向理解。

6. 姓名 告诉 形成一种 使用 它。

阅读explain_尤为重要姓名_or_die 为“解释(姓名 或者死)”。
使用一致的解释名称空间前缀在
显而易见性部门,以及。

名称中单词的顺序也指示参数的顺序。 论据
总是列出 结束 与传递给系统调用的参数相同; 所有 of 他们。 如果
_errno_ 出现在名称中,它的参数总是在系统调用参数之前。 如果
_message_ 出现在名称中,它的两个参数总是在前。

5. Do it or it 打破 at 运行。

libexplain 库检测假指针和许多其他假系统调用参数,
并且通常即使在最艰难的情况下也会尝试避免段错误。 这应该
永远不会在运行时中断,但更多的实际使用无疑会改善这一点。

一些错误消息是针对开发人员和维护人员而不是最终用户的,因为这
可以协助解决错误。 与其说是“在运行时中断”,不如说是“在
运行时”(在系统调用 barfs 之后)。

4. 关注 常见 公约 你会 得到 it 对。

因为 C 没有命名空间,所以 libexplain 库总是使用一个解释名称
字首。 这是创建伪名称空间的传统方法,以避免
符号冲突。

所有 libexplain 调用的尾随参数与它们的系统调用相同
正在描述。 这是为了提供与
系统调用自己。

3. 阅读 文件 你会 得到 it 对。

libexplain 库旨在为每个人提供完整的 Doxygen 文档
公共 API 调用(以及内部调用)。

MESSAGE 内容


使用 libexplain 有点像在汽车启动时查看汽车底部
机械师的起重机。 下面有一些丑陋的东西,还有泥巴和碎屑,还有
用户很少看到它。 一个好的错误信息需要提供信息,即使对于一个用户
很幸运不必经常看底部,而且
为机械师通过电话听取用户描述提供信息。 这是
不是一件容易的事。

回顾我们的第一个例子,如果它使用 libexplain,代码将是这样的:
int fd = Explain_open_or_die("some/thing", O_RDONLY, 0);
将失败并显示这样的错误消息
open(pathname = "some/file", flags = O_RDONLY) 失败,没有那个文件或目录
(2、ENOENT) 因为当前目录下没有“some”目录
这分为三部分
系统调用 失败的, 系统错误 因为
解释

之前 计划
可能会看到“因为”之前的消息部分对非技术性而言过于技术化
技术用户,主要是因为在
错误信息的开头。 它看起来像 痕迹(1) 输出,用于奖励极客
点。
open(pathname = "some/file", flags = O_RDONLY) 失败,没有那个文件或目录
(2、ENOENT)
这部分错误信息对开发人员编写代码时必不可少,
对于必须阅读错误报告并修复错误的维护者来说同样重要
代码。 它准确地说明了失败的原因。

如果此文本未呈现给用户,则用户无法将其复制并粘贴到
错误报告,如果它不在错误报告中,维护者就无法知道实际发生了什么
错误。

技术人员经常会使用 痕迹(1)或 桁架(1) 得到这个确切的信息,但是
阅读错误报告时,此途径未打开。 漏洞报告者的系统很远
远,而且,现在,处于完全不同的状态。 因此,这些信息需要在
错误报告,这意味着它必须在错误消息中。

系统调用表示还为消息的其余部分提供上下文。 如果需要
出现时,可以在解释中按名称引用违规系统调用参数
在“因为”之后。 另外,所有的字符串都是全引号转义的C字符串,所以
嵌入的换行符和非打印字符不会导致用户终端运行
干草线。

系统错误 是什么出来的 冲突(2)、加上错误符号。 不耐烦和
专家系统管理员此时可以停止阅读,但作者迄今为止的经验是
进一步阅读是有益的。 (如果它没有回报,它可能是一个领域
可以改进的 libexplain。 当然,欢迎代码贡献。)

计划
这是针对非技术用户的错误消息部分。 它看起来超越
简单的系统调用参数,并寻找更具体的东西。
当前目录中没有“some”目录
这部分试图用通俗的语言解释错误的近因,并且
在这里,国际化是必不可少的。

一般来说,策略是包含尽可能多的信息,以便用户
不需要去寻找它(并且不会将它排除在错误报告之外)。

国际化
libexplain 库中的大多数错误消息都已国际化。 那里
还没有本地化,所以如果你想用你的母语解释,
请贡献。

上面的“大多数”限定词与概念验证有关
实施不包括国际化支持。 代码库正在
逐步修改,通常是重构消息的结果,以便每个错误
消息字符串在代码中只出现一次。

已经为需要组装部分的语言做出了规定
系统调用 失败的, 系统错误 因为 解释
在本地化错误消息中以不同的顺序正确语法。

尸检
有些时候程序还没有使用 libexplain,而你不能使用 痕迹(1)
任何一个。 有一个 说明(1) 包含在 libexplain 中的命令,可用于
如果底层系统的状态没有太大变化,则解密错误消息。
$ 说明 重命名 FOO /tmp/酒吧/巴兹 -e 恩诺恩
rename(oldpath = "foo", newpath = "/tmp/bar/baz") 失败,没有那个文件或目录
(2、ENOENT)因为newpath中没有“bar”目录/ tmp目录“ 目录
$
请注意如何使用系统调用参数名称解​​决路径歧义。 的
当然,你必须知道错误和系统调用 说明(1) 有用。 作为
除此之外,这是 libexplain 自动测试套件用来验证
libexplain 正在工作。

哲学
“告诉我一切,包括我不知道要寻找的东西。”

该库的实现方式是,当静态链接时,只有您的代码
实际使用将被链接。 这是通过每个源文件有一个函数来实现的,
只要可行。

当可以提供更多信息时,libexplain 会这样做。 用户越少
还得自己去追查,越好。 这意味着 UID 伴随着
用户名,GID 伴随着组名,PID 伴随着进程
名称、文件描述符和流都带有路径名, .

解析路径时,如果路径组件不存在,libexplain 会寻找类似的
名称,以便为排版错误建议替代方案。

libexplain 库尝试使用尽可能少的堆,通常不使用。 这是
尽可能避免扰乱进程状态,尽管有时
不可避免的。

libexplain 库试图通过避免全局变量,保持线程安全
尽可能多地在堆栈上。 有一个公共消息缓冲区,并且
使用它的函数被记录为不是线程安全的。

libexplain 库不会干扰进程的信号处理程序。 这使得
确定指针是否会对挑战进行段错误,但并非不可能。

当信息可以通过系统调用获得,也可以通过 / proc中
入口,系统调用是首选。 这是为了避免干扰进程的状态。
有时也没有可用的文件描述符。

libexplain 库是在大文件支持下编译的。 没有大/小
精神分裂症。 如果这会影响 API 中的参数类型,则会发出错误
如果缺少必要的大文件定义。

FIXME:需要确保在代码中处理文件系统配额。 这个
适用于某些 获取限制(2) 边界,以及。

在某些情况下,亲戚路径不提供信息。 例如:系统守护进程,
服务器和后台进程。 在这些情况下,错误中使用了绝对路径
解释。

PATH 分辨率


简短版本:见 路径分辨率(7)。

长版:大多数用户从未听说过 路径分辨率(7)、许多高级用户
从来没有读过它。 这是一个带注释的版本:

1: Start 开始 of 分辨率 过程
如果路径名以斜杠(“/”)字符开头,则起始查找目录为
调用进程的根目录。

如果路径名不以斜杠(“/”)字符开头,则开始查找
解析进程的目录是进程的当前工作目录。

2: 徒步 沿
将当前查找目录设置为起始查找目录。 现在,对于每个非
路径名的最后一个组成部分,其中一个组成部分是由斜杠(“/”)分隔的子字符串
字符,在当前查找目录中查找此组件。

如果进程对当前查找目录没有搜索权限,则 EACCES
返回错误(“权限被拒绝”)。
open(pathname = "/home/archives/.ssh/private_key", flags = O_RDONLY) 失败,
Permission denied (13, EACCES) 因为进程没有搜索权限
到路径名“/home/archives/.ssh”目录下,进程有效GID 1000
"pmiller" 与目录所有者 1001 "archives" 不匹配,因此所有者
权限模式“rwx”被忽略,其他权限模式是“---”,并且
进程没有特权(没有 DAC_READ_SEARCH 能力)

如果未找到该组件,则返回 ENOENT 错误(“没有此类文件或目录”)。
unlink(pathname = "/home/microsoft/rubbish") 失败,没有那个文件或目录 (2,
ENOENT),因为路径名中没有“microsoft”目录“/主页“ 目录

当用户输入错误路径名时,也有一些支持,在用户输入错误时提出建议
返回 ENOENT:
open(pathname = "/user/include/fcntl.h", flags = O_RDONLY) 失败,没有这样的文件或
目录(2,ENOENT)因为路径名“/”中没有“user”目录
目录,您是指“usr”目录吗?

如果找到该组件,但既不是目录也不是符号链接,则 ENOTDIR
返回错误(“不是目录”)。
open(pathname = "/home/pmiller/.netrc/lca", flags = O_RDONLY) 失败,不是一个
目录(20,ENOTDIR)因为路径名中的“.netrc”常规文件
“/home/pmiller”目录被用作目录,当它不是

如果找到组件并且是一个目录,我们将当前查找目录设置为
目录,然后转到下一个组件。

如果找到组件并且是符号链接(symlink),我们首先解析这个符号链接
链接(以当前查找目录作为起始查找目录)。 出错时,那
返回错误。 如果结果不是目录,则返回 ENOTDIR 错误。
unlink(pathname = "/tmp/dangling/rubbish") 失败,没有那个文件或目录 (2,
ENOENT) 因为路径名中的“悬空”符号链接“/ tmp目录“ 目录
指不存在的“无处”
如果符号链接解析成功并返回一个目录,我们设置当前
查找目录到该目录,然后转到下一个组件。 请注意,
这里的解析过程涉及递归。 为了保护内核免受堆栈
溢出,也为了防止拒绝服务,有最大限制
递归深度,以及所遵循的最大符号链接数。 ELOOP 错误是
超过最大值时返回(“符号链接的级别太多”)。
open(pathname = "/tmp/dangling", flags = O_RDONLY) 失败,太多级别
符号链接 (40, ELOOP) 因为在
路径名,从“/tmp/dangling”开始
如果符号链接太多,也可能会出现 ELOOP 或 EMLINK 错误,但没有
检测到循环。
open(pathname = "/tmp/rabbit-hole", flags = O_RDONLY) 失败,太多级别
符号链接 (40, ELOOP) 因为在
路径名 (8)
注意实际限制是如何打印的。

3: 找到最适合您的地方 最后 条目
路径名的最后一个部分的查找与所有其他部分的查找一样
组件,如上一步所述,有两个不同之处:

(i) 最终组件不必是目录(至少就路径解析而言
过程有关。 它可能必须是目录或非目录,因为
具体系统调用的要求)。

(ⅱ)
如果没有找到最终的组件,不一定是错误; 也许我们只是
创建它。 最终条目的处理细节在
特定系统调用的手册页。

(三)
最后一个组件如果是符号链接也有可能出问题
它不应该被遵循。 例如,使用 打开(2) O_NOFOLLOW 标志:
open(pathname = "a-symlink", flags = O_RDONLY | O_NOFOLLOW) 失败,太多级别
符号链接 (ELOOP) 因为指定了 O_NOFOLLOW 但路径名指的是
象征性的联系

(四)
用户在输入路径名时出错是很常见的。 libexplain 库
尝试在返回 ENOENT 时提出建议,例如:
open(pathname = "/usr/include/filecontrl.h", flags = O_RDONLY) 失败,没有那个文件或
目录(2,ENOENT)因为路径名中没有“filecontrl.h”常规文件
"/ usr / include" 目录,你的意思是 "fcntl.h" 常规文件吗?

(v) 也有可能要求最终组件不是一个
常规文件:
readlink(pathname = "just-a-file", data = 0x7F930A50, data_size = 4097) 失败,
无效参数 (22, EINVAL) 因为路径名是一个普通文件,而不是一个符号链接

(六)
FIXME:处理“t”位。

极限
关于路径名和文件名有许多限制。

路径名长度限制
路径名有最大长度。 如果路径名(或一些中间
解析符号链接时获得的路径名)太长,一个 ENAMETOOLONG
返回错误(“文件名太长”)。 注意如何包含系统限制
在错误消息中。
打开(路径名 =“很长", flags = O_RDONLY) 失败,文件名太长 (36,
ENAMETOOLONG) 因为路径名超过了系统最大路径长度 (4096)

文件名长度限制
某些 Unix 变体对每个路径组件中的字节数有限制。
有的默默处理这个,有的给ENAMETOOLONG; libexplain
图书馆用途 路径配置(3) _PC_NO_TRUNC 告诉哪个。 如果发生此错误,则
libexplain 库将在错误消息中说明限制,限制是
从...获取 路径配置(3) _PC_NAME_MAX。 注意如何包含系统限制
在错误消息中。
打开(路径名 =“system7/只有14个字符", flags = O_RDONLY) 失败,文件
名称太长(36,ENAMETOOLONG),因为“only-had-14-characters”组件是
长于系统限制 (14)

空路径名
在原始 Unix 中,空路径名指的是当前目录。
现在 POSIX 规定不能成功解析空路径名。
open(pathname = "", flags = O_RDONLY) 失败,没有那个文件或目录 (2,
ENOENT) 因为 POSIX 规定不得解析空路径名
顺利

权限
文件的权限位由三组三位组成。 第一组
当调用进程的有效用户 ID 等于调用进程的所有者 ID 时使用三
文件。 当文件的组 ID 等于
调用进程的有效组ID,或者是调用进程的补充组ID之一
调用过程。 当两者都不成立时,使用第三组。
打开(路径名 =“/ etc / passwd文件", flags = O_WRONLY) 失败,权限被拒绝 (13,
EACCES),因为该进程没有“passwd”常规的写权限
路径名中的文件“/等" 目录,进程有效 UID 1000 "pmiller"
与常规文件所有者 0 "root" 不匹配,因此所有者权限模式为 "rw-"
被忽略,others权限模式为“r--”,进程没有特权
(没有 DAC_OVERRIDE 功能)
这个解释有相当大的空间,因为大多数用户不知道这个
这就是权限系统的工作原理。 特别是:所有者、组和其他
权限是独占的,它们不是“或”在一起的。

奇怪 AND 有趣 系统 通话


为每个系统调用编写一个特定的错误处理程序的过程常常揭示
有趣的怪癖和边界条件,或晦涩难懂 埃尔诺(3) 价值观。

内蒙, 没有 中等 发现
复制 CD 的行为是本文标题的来源。
$ dd 如果=/dev/cdrom 的=fubar.iso
dd:打开“/dev/cdrom”:未找到介质
$
作者想知道为什么他的电脑告诉他没有通灵者这样的东西
中等的。 除了大量以英语为母语的人不是
甚至知道“media”是复数,更不用说“medium”是它的单数了,字符串
由...返回 冲突(3) 因为 ENOMEDIUM 非常简洁,几乎完全没有
内容。

什么时候 打开(2) 返回 ENOMEDIUM 如果 libexplain 库可以扩展一个
很少在此,基于驱动器的类型。 例如:
...因为软驱中没有磁盘
... 因为 CD-ROM 驱动器中没有光盘
... 因为磁带机中没有磁带
...因为读卡器中没有记忆棒

于是事情就这么过去了……
open(pathname = "/dev/cdrom", flags = O_RDONLY) 失败,找不到介质 (123,
ENOMEDIUM) 因为 CD-ROM 驱动器中似乎没有光盘
作者以前不知道的技巧是使用
O_NONBLOCK 标志,这将允许您打开一个没有介质的驱动器。 然后是你
特定于设备的问题 读写控制(2) 请求直到你弄清楚它到底是什么。 (不是
确定这是否是 POSIX,但它似乎在 BSD 和 Solaris 中也可以这样工作,根据
沃迪姆(1) 来源。)

还要注意上下文中“磁盘”和“磁盘”的不同用法。 CD标准起源
在法国,但其他所有东西都有一个“k”。

失败, 地址
任何采用指针参数的系统调用都可以返回 EFAULT。 libexplain 库
可以找出哪个参数有问题,并且在不干扰过程的情况下进行
(或线程)信号处理。

可用时, 微核(2) 使用系统调用,询问内存区域是否有效。
它可以返回三个结果:mapped but not in physical memory,mapped and in physical
内存,而不是映射。 在测试一个指针的有效性时,前两个是“是”
最后一个是“不”。

检查 C 字符串更困难,因为我们只需要检查指针和大小,而不是指针和大小
有一个指针。 为了确定大小,我们必须找到 NUL,这可以
段错误,catch-22。

为了解决这个问题,libexplain 库使用 统计(2) sysem 调用(具有已知的
好的第二个参数)来测试 C 字符串的有效性。 失败返回 && errno == EFAULT
是“否”,其他都是“是”。 这当然将字符串限制为 PATH_MAX
字符,但这对于 libexplain 库来说通常不是问题,因为那是
几乎总是它关心的最长的字符串。

电子档, 许多 打开
当进程已打开最大数量的文件描述符时,会发生此错误。
如果要打印实际限制,并且 libexplain 库尝试打印,则无法打开
一个文件在 / proc中 阅读它是什么。
open_max = sysconf(_SC_OPEN_MAX);
这个并不难,有一个 系统配置(3) 获取限额的方式。

申请, 许多 打开 in 系统
当系统对打开文件总数的限制已被
到达。 在这种情况下没有方便 系统配置(3) 获取极限的方式。

深入挖掘,人们可能会发现在 Linux 上有一个 / proc中 我们可以阅读的条目
获得这个值。 Catch-22:我们的文件描述符用完了,所以我们不能打开一个文件
阅读极限。

在 Linux 上有一个系统调用来获取它,但它没有 [e]glibc 包装函数,所以
你必须非常小心:

解释最大文件(无效)
{
#ifdef __linux__
结构 __sysctl_args 参数;
int32_t 最大文件;
size_t 最大文件大小 = 大小(最大文件);
整数名称[] = { CTL_FS, FS_MAXFILE };
memset(&args, 0, sizeof(struct __sysctl_args));
args.name = 名称;
args.nlen = 2;
args.oldval = &maxfile;
args.oldlenp = &maxfile_size;
如果(系统调用(SYS__sysctl,&args)>= 0)
返回最大文件;
#ENDIF
return -1;
}
这允许在可用时将限制包含在错误消息中。

有效值 “无效的 争论” vs 埃诺西斯 “功能 不能 实施的”
不受支持的操作(例如 符号链接(2) 在 FAT 文件系统上) 未报告
从一个系统调用到下一个系统调用始终如一。 可以有 EINVAL 或
ENOSYS 回来了。

因此,必须注意这些错误情况以使其正确,尤其是
因为 EINVAL 也可能指的是一个或多个系统调用参数的问题。

备注 埃尔诺(3) is 不能 时刻
有时需要阅读 [e]glibc 源代码以确定如何以及
当某些系统调用返回错误时。

关注(3) 文件编号(3)
通常假设这些函数不会返回错误。 这仅当
论点是有效的,但是他们能够检测到无效的
指针。

路径配置(3) 路径配置(3)
的返回值 路径配置(2)和 路径配置(2) 可以合法地为 -1,所以它是
有必要看看 埃尔诺(3) 已明确设置。

读写控制(2)
的返回值 读写控制(2) 可以合法地为 -1,因此有必要查看是否
埃尔诺(3) 已明确设置。

读取目录(3)
的返回值 读取目录(3) 对于错误和文件结尾都是 NULL。 这是
有必要看看 埃尔诺(3) 已明确设置。

设置缓冲区(3) 设置缓冲区(3) 设置线缓冲区(3) 设置缓冲区(3)
除了最后一个函数外,所有函数都返回 void。 和 设置缓冲区(3) 仅记录为
出错时返回“非零”。 有必要看看 埃尔诺(3) 已经明确
设置。

斯特德(3) 斯特罗(3) 强行(3) 漫步(3) 斯特尔图尔(3) 斯特劳尔(3)
这些函数在出错时返回 0,但这也是一个合法的返回值。 这是
有必要看看 埃尔诺(3) 已明确设置。

(3)
虽然 ANSI C 标准只要求备份一个字符,但它变成了
[e]glibc 允许更多......但这意味着它可能会因 ENOMEM 而失败。 它可以
如果 EBADF 也会失败 fp 是假的。 最困难的是,如果您传递 EOF 错误
发生返回,但未设置 errno。

libexplain 库正确检测所有这些错误,即使在
错误值的记录很差,如果有的话。

ENOSPC, 没有 空间 on 设备
当此错误涉及文件系统上的文件时,libexplain 库会打印挂载
点文件系统有问题。 这可以使错误的来源很多
更清晰。
写入(fildes = 1 “示例”,数据 = 0xbfff2340,数据大小 = 5)失败,没有剩余空间
在设备 (28, ENOSPC) 上,因为文件系统包含 fildes ("/主页“) 没有
更多的数据空间
随着更多特殊设备支持的添加,错误消息预计将包含设备
设备的名称和实际大小。

EROFS, 只读 文件 系统
当此错误涉及文件系统上的文件时,libexplain 库会打印挂载
点文件系统有问题。 这可以使错误的来源很多
更清晰。

随着更多特殊设备支持的添加,错误消息预计将包含设备
名称和类型。
open(pathname = "/dev/fd0", O_RDWR, 0666) 失败,只读文件系统 (30, EROFS)
因为软盘设置了写保护标签

...因为 CD-ROM 不可写
...因为存储卡设置了写保护标签
...因为 ½ 英寸磁带没有写环

重命名
重命名(2) 系统调用用于改变文件的位置或名称,移动它
如果需要,在目录之间。 如果目标路径名已经存在,它将是
以原子方式替换,因此没有其他进程试图
访问它会发现它丢失了。

但是有一些限制:您只能重命名另一个目录之上的目录
directory 如果目标目录不为空。
rename(oldpath = "foo", newpath = "bar") 失败,目录不为空 (39,
ENOTEMPTY) 因为 newpath 不是空目录; 也就是说,它包含条目
以外 ”。” 和 ”..”
您也不能在非目录之上重命名目录。
重命名(oldpath =“foo”,newpath =“bar”)失败,不是目录(20,ENOTDIR)
因为 oldpath 是一个目录,而 newpath 是一个普通文件,而不是一个目录
也不允许反向
rename(oldpath = "foo", newpath = "bar") 失败,是目录(21,EISDIR)
因为 newpath 是一个目录,而 oldpath 是一个普通文件,而不是一个目录

当然,这会使 libexplain 库的工作变得更加复杂,因为
取消链接(2)或 命令rmdir(2) 系统调用被隐式调用 重命名(2),所以所有的
取消链接(2)或 命令rmdir(2) 错误也必须被检测和处理。

重复2
重复2(2) 系统调用用于创建引用
与第一个文件描述符相同的对象。 通常这用于实现shell输入
和输出重定向。

有趣的是,就像 重命名(2) 可以原子地重命名一个文件
现有文件并删除旧文件, 重复2(2) 可以对已经打开的文件执行此操作
描述。

再一次,这使得 libexplain 库的工作变得更加复杂,因为 关闭(2)
系统调用被隐式调用 重复2(2),所以所有的 关闭(2) 的错误必须是
检测和处理,以及。

冒险活动 IN 国际奥委会 客户服务


读写控制(2) 系统调用为设备驱动作者提供了一种通信方式
不适合现有内核 API 的用户空间。 看 读写控制列表(2)。

解码 请求 数值
从粗略的看 读写控制(2) 界面,会出现一个大而有限的
可能的数量 读写控制(2) 请求。 每个不同 读写控制(2)请求有效
另一个系统调用,但根本没有任何类型安全 - 编译器无法帮助
程序员做对了。 这可能是背后的动机 tflush(3)和
朋友。

最初的印象是你可以解码 读写控制(2) 请求使用一个巨大的开关
陈述。 结果证明这是不可行的,因为人们很快就会发现它是
不可能包括所有必要的系统头文件定义各种 读写控制(2)
请求,因为他们很难相互配合。

更深入的观察表明存在一系列“私人”请求编号,并且设备
鼓励驱动程序作者使用它们。 这意味着有更大的可能
一组请求,具有模糊的请求编号,而不是立即显而易见。 还,
也有一些历史上的歧义。

我们已经知道切换是不切实际的,但现在我们知道选择
适当的请求名称和解释我们不仅要考虑请求编号,还要考虑
还有文件描述符。

实施 读写控制(2) libexplain 库内的支持是有一个表
指向 读写控制(2) 请求描述符。 这些描述符中的每一个都包括一个可选的
指向消歧函数的指针。

每个请求实际上是在一个单独的源文件中实现的,以便必要的
包含文件免除了与他人友好相处的义务。

代表性
libexplain 库背后的理念是提供尽可能多的信息
可能,包括系统调用的准确表示。 如果是
读写控制(2) 这意味着打印正确的请求编号(按名称)以及正确的(或
至少有用)第三个参数的表示。

读写控制(2) 原型是这样的:
int ioctl(int fildes, int request, ...);
这应该会让你的类型安全警报响起。 [e]glibc 的内部,这被打开了
成多种形式:
int __ioctl(int fildes, int request, long arg);
int __ioctl(int fildes, int request, void *arg);
并且 Linux 内核系统调用接口期望
asmlinkage long sys_ioctl(无符号整数字段,无符号整数请求,无符号长
参数);
第三个参数的极端可变性是一个挑战,当 libexplain 库
尝试打印第三个参数的表示。 但是,一旦请求数
已消除歧义,libexplain 库的 ioctl 表中的每个条目都有一个
自定义 print_data 函数(OO 手动完成)。

说明
确定要使用的解释的问题较少。 一旦请求号
已消除歧义,libexplain 库的 ioctl 表中的每个条目都有一个自定义
print_explanation 函数(同样,OO 手动完成)。

与第 2 节和第 3 节系统调用不同,大多数 读写控制(2) 请求没有错误
记录在案。 这意味着,为了给出好的错误描述,有必要阅读内核
发现的来源

· 什么 埃尔诺(3) 可能会返回值,并且

· 每个错误的原因。

由于使用内核进行函数调用调度的 OO 性质,您需要阅读
所有 实施该消息的来源 读写控制(2) 请求,而不仅仅是泛型实现。 它
可以预料,不同的内核会有不同的错误编号,并且巧妙地
不同的错误原因。

有效值 vs 埃诺蒂
情况更糟 读写控制(2) 请求比系统调用,用 EINVAL 和
ENOTTY 都被用来表示一个 读写控制(2) 请求是不恰当的
上下文,偶尔还有 ENOSYS、ENOTSUP 和 EOPNOTSUPP(用于套接字)作为
好。 Linux 内核源代码中的一些注释似乎表明渐进式
清理工作正在进行中。 对于额外的混乱,BSD 在混乱中添加了 ENOIOCTL。

因此,必须注意这些错误情况以使其正确,尤其是
因为 EINVAL 也可能指的是一个或多个系统调用参数的问题。

intptr_t
C99 标准定义了一个整数类型,保证能够保存任何指针
没有表示损失。

上面的函数 syscall 原型会更好写
长 sys_ioctl(无符号 int fildes, 无符号 int 请求, intptr_t arg);
问题是由特定于设备或特定于文件系统引起的认知失调
读写控制(2) 实现,例如:
long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
多数 读写控制(2) requests 实际上有一个 int *arg 第三个参数。 但拥有它
声明 long 导致代码将其视为 long *arg。 这对 32 位是无害的
(sizeof(long) == sizeof(int)) 但在 64 位上很讨厌 (sizeof(long) != sizeof(int))。
取决于字节序,你得到或没有得到你期望的值,但是你 时刻 得到
记忆涂鸦或堆栈涂鸦。

把所有这些写成
int ioctl(int fildes, int request, ...);
int __ioctl(int fildes, int request, intptr_t arg);
长 sys_ioctl(无符号 int fildes, 无符号 int 请求, intptr_t arg);
长 vfs_ioctl(struct file *filp, unsigned int cmd, intptr_t arg);
强调整数只是一个整数来表示几乎
总是一个不相关的指针类型。

结论


使用 libexplain,您的用户会喜欢它。

版权


libexplain 1.4 版
版权所有 (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Peter Miller

在线使用explain_lca2010 使用onworks.net 服务


免费服务器和工作站

下载 Windows 和 Linux 应用程序

  • 1
    斯威格
    斯威格
    SWIG 是一种软件开发工具
    连接用 C 编写的程序和
    C++ 具有各种高级
    编程语言。 SWIG 用于
    不同的...
    下载痛饮
  • 2
    WooCommerce Nextjs 反应主题
    WooCommerce Nextjs 反应主题
    React WooCommerce 主题,构建于
    Next JS、Webpack、Babel、Node 和
    Express,使用 GraphQL 和 Apollo
    客户。 React 中的 WooCommerce 商店(
    包含:产品...
    下载 WooCommerce Nextjs React 主题
  • 3
    archlabs_repo
    archlabs_repo
    ArchLabs 的软件包仓库 这是一个
    也可以获取的应用程序

    https://sourceforge.net/projects/archlabs-repo/.
    它已在 OnWorks 中托管...
    下载 archlabs_repo
  • 4
    和风项目
    和风项目
    Zephyr 项目是新一代
    实时操作系统 (RTOS)
    支持多种硬件
    架构。 它基于一个
    小尺寸内核...
    下载 Zephyr 项目
  • 5
    SCCons
    SCCons
    SCons是一个软件构建工具
    这是一个更好的选择
    经典的“Make”构建工具
    我们都知道并热爱。 SCons 是
    实施了一个...
    下载 SCons
  • 6
    聚苯乙烯
    聚苯乙烯
    PSeInt 是一个伪代码解释器
    讲西班牙语的编程学生。
    它的主要目的是成为一个工具
    学习和理解基本的
    概念...
    下载 PSeInt
  • 更多 ”

Linux 命令

Ad