文件 I/O 进阶与登录记账 — lseek、空洞文件、utmp/wtmp
文件 I/O 进阶与登录记账 — lseek、空洞文件、utmp/wtmp
这一章讲什么: 两件事。第一,lseek 的全部本事——把读写指针挪到 文件任意位置(包括文件尾之后一万字节),由此引出「文件空洞」和「稀疏 文件」:一个
ls -l显示 1,000,020 字节的文件,实际只占 8,192 字节磁盘。 第二,登录记账体系——Unix 把谁何时登录登出记在三个二进制文件里 (lastlog、utmp、wtmp),本章读完它们的格式、语义差别和访问 API, 并仿写出 lastlog 和 last 两个命令。 项目密度最高的一章,也是「读真实系统文件」的第一次实战。
1. 顶层全景:为什么需要移动文件偏移
第 4 章:文件 = 字节流,顺序读写(偏移自动前进)
│ 新问题:记录型文件(每个用户一条定长记录)要求「直接跳到第 N 条」
▼
lseek:三个基准(SEEK_SET/CUR/END)× 任意偏移
│ 偏移可以越过文件尾 → 写数据 → 文件空洞(稀疏文件)
▼
应用一:lastlog 文件 = 按 UID 索引的稀疏记录数组
应用二:wtmp 文件 = 顺序追加的事件日志,但 last 命令要逆着读
│ 登录记录怎么读?→ utmpx API / getpwent 遍历用户库
▼
项目:spl_lastlog、spl_utmpdump、spl_last图说:一条 lseek 主线串起全章。 主线例子:spl_lastlog 读一条
用户登录记录——输入一个 UID,文件偏移从 0 跳到 UID × 结构大小 处,
读出 292 字节,判断 ll_time 是否为 0。每一步都有具体的数。
2. lseek 与文件空洞
三个参数,两个妙用
off_t lseek(int fd, off_t offset, int whence);whence 决定从哪儿起跳:SEEK_SET(文件头)、SEEK_CUR(当前偏移)、
SEEK_END(文件尾);offset 可正可负1。成功的返回值是新偏移的
绝对位置,于是两个零成本小技巧: lseek(fd, 0, SEEK_CUR) 查当前位置、
lseek(fd, 0, SEEK_END) 查文件大小。挪到文件头之前是错误(EINVAL)2。
主线例子的第一步:makefilehole——先造一个带洞的文件
程序做三件事:写 "0123456789"(10 字节)→ lseek(fd, 1000000, SEEK_END)
→ 再写同样的 10 字节。结果:
$ ls -l file_with_hole
-rw-r--r-- 1 stewart stewart 1000020 file_with_hole ← 逻辑大小:10 + 1,000,000 + 10
$ ls -s --block-size=1 file_with_hole
8192 file_with_hole ← 实际占用:2 个 4096 字节块
$ du --block-size=1 file_with_hole
8192图说:中间 1,000,000 字节是「洞」——读它时 read 不报错,而是返回
一串 0(NULL 字节);磁盘上根本不分配3。第二个数据块的起点必须
是 4096 的倍数:1,000,010 之前最大的 4096 倍数是 999,424,所以那 10 个
字符落在第二块内偏移 586 处,块内其余字节全是 04。od -a -Ad 能直接
看到:地址 0 处是 0123456789,地址 1,000,000 处又是 0123456789,
中间用 * 折叠的 16 字节全零行5。
稀疏文件就是「逻辑大小远大于实际磁盘占用」的文件。数据库、虚拟机
磁盘镜像、lastlog(下一节)都是。教训:ls -l 的大小不等于磁盘成本,
ls -s / du 才是6。
3. lastlog:一个按 UID 索引的稀疏数据库
文件的形状,从 man 页的 CAVEATS 里推理出来
lastlog(8) 报告每个用户最近一次登录。man 页 CAVEATS 一句「UID 跳空
会让 lastlog 长时间无输出」,加上「文件是稀疏的」,原书推理出它的结构:
一个以 UID 为下标的巨型定长记录数组——UID 为 m 的用户的记录就在
字节偏移 m × sizeof(struct lastlog) 处;从没登录过的用户没有记录,
那一格就是文件洞,程序照样要读过去(所以「无输出但费时间」)7。
图说:整条「从文档碎片推理数据结构」的推理链值得记住—— CAVEATS 的抱怨 + 稀疏性 + 大小随 UID 增长,三条线索推出数组布局。
记录结构与一个跨架构的坑
struct lastlog 在 <lastlog.h>(它只有四行,真正定义在
<bits/utmp.h>)里8:
struct lastlog {
#if __WORDSIZE_TIME64_COMPAT32
int32_t ll_time; /* 32 位兼容模式下是 4 字节 */
#else
__time_t ll_time; /* 否则是 time_t(64 位机上 8 字节) */
#endif
char ll_line[32]; /* 登录终端,如 pts/2 */
char ll_host[256]; /* 远程主机 */
};图说:没有 UID 字段——位置本身就是 UID;没有用户名字段——要查 passwd
库9。ll_time 的类型随 __WORDSIZE_TIME64_COMPAT32 而变
(x86-64 跑 32 位应用兼容模式时为 4 字节),所以不能直接把它强转成
time_t* 传给 localtime(),必须先赋给一个 time_t 变量——
指针强转不改底层宽度,赋值才做转换10。原书的可移植读法:
time_t ll_time = ll_entry.ll_time; /* 赋值,做宽度转换 */
bdtime = localtime(&ll_time);配套的底层功夫
- 系统文件路径别硬编码:
<paths.h>提供_PATH_LASTLOG、_PATH_WTMP等宏,换发行版路径变了程序照样找得到11。 - 找头文件的真身:
cpp -v /dev/null -o /dev/null列出预处理器的 include 搜索顺序,<bits/utmp.h>就藏在其中某个目录(通常/usr/include/x86_64-linux-gnu/bits/)12。 - 读一个结构体的惯用法:
read(fd, &rec, sizeof(rec))后三查——<0是错误、==sizeof成功、其余是「不完整读」13。
4. passwd 数据库:从 UID 找名字
lastlog 记录里没有用户名,只有隐含的 UID。/etc/passwd 是明文数据库,
每行七个冒号分隔字段:用户名:口令占位:UID:GID:注释:主目录:登录shell。
(第二字段的 x 表示真口令存在 /etc/shadow)14。
接口一族(声明在 <pwd.h>)15:
| 函数 | 作用 |
|---|---|
getpwuid(uid) | 按 UID 查,返回 struct passwd* |
getpwnam(名) | 按用户名查 |
setpwent() / getpwent() / endpwent() | 遍历全部记录的三件套(迭代器) |
getpwuid_r 等 _r 版 | 线程安全版,调用者提供缓冲区 |
两个坑:返回的指针指向静态存储,再调同类函数会被覆盖,要用就先拷贝16;
getpwent() 读到文件尾时会设 errno=ENOENT(man 页没写),
遍历循环判结束时得排除它17。
5. 主线例子走查:spl_lastlog 的一条记录
程序逻辑(每步有数):
① open(_PATH_LASTLOG, O_RDONLY) → ll_fd
② ll_file_size = lseek(ll_fd, 0, SEEK_END) → 比如 292,000 字节
③ highest_uid = 292000 / sizeof(struct lastlog) - 1
(sizeof(struct lastlog) 在该系统上 = 292:8+32+256 含填充)
④ setpwent(); → 开始遍历 passwd
⑤ 对每条 passwd 记录:uid = pw_uid
uid > highest_uid? → 打印 "**Never logged in**"(从没登录过)
否则 lseek(ll_fd, 292 × uid, SEEK_SET) → 跳到该用户的格子
read(ll_fd, &ll_entry, 292) → 读一条记录
ll_entry.ll_time == 0? → "**Never logged in**"(格子是洞)
否则 ll_time 赋给 time_t → localtime → strftime("%c") → 打印
⑥ close + endpwent图说:三类「没有登录记录」——UID 超出文件范围(②③)、读进的是洞
(ll_time==0)、passwd 里根本没有这个人(遍历天然排除)。
对照真命令的输出,差异只在日期格式(%c vs "%a %b %e %H:%M:%S %z %Y",
后者是从 shadow-utils 源码里查证的)18。
两种读文件范式的分工
原书在这里提炼出一对概念,spl_lastlog 正好是混合体19:
| 范式 | 谁做偏移管理 | 代价与收益 |
|---|---|---|
| 显式 seek+read | 你自己 | 精确控制、无 API 依赖;但容易错、要自己管性能 |
| API 式读取(getpwent/getutxent) | 库的程序员 | 代码简单;API 变了你得跟着变 |
选择判据:文件格式稳定、随机访问需求强 → 显式;有现成库、顺序遍历 → API。
判断(我们的,不是书里的): 「API 式读取」还有个隐形的坑原书 没展开:这些迭代器(getpwent/getutxent)不是线程安全的、还依赖进程级 全局状态,多线程程序里一个线程在遍历 passwd,另一个线程调了 getpwuid, 迭代位置就被毁了。第 16 章之后写线程程序,要么用
_r版,要么自己加锁。 如果错,会错在: 单线程程序里这个坑根本不存在——原书的教学场景 全是单线程,所以略过它是合理的简化,不是疏漏。
6. wtmp 与 utmp:事件流与它的两种读法
utmp 结构:不只记登录
struct utmp(Linux 上 <utmpx.h> 的 utmpx 与它是同一布局)的
核心是 ut_type 字段——同一个文件里混存十种事件20:
EMPTY=0 RUN_LVL=1 BOOT_TIME=2 NEW_TIME=3 OLD_TIME=4
INIT_PROCESS=5 LOGIN_PROCESS=6 USER_PROCESS=7 DEAD_PROCESS=8 ACCOUNTING=9用户登录 = USER_PROCESS(带用户名、终端线 pts/1、来源主机);
登出 = DEAD_PROCESS(用户名常被清空,但不一定——man 页原话:
「空用户名表示该终端登出,反之不成立」)21。一条记录的其余字段:
ut_line(终端)、ut_tv(时间戳)、ut_host(远程主机)、
ut_pid、ut_exit(进程退出状态)等22。
utmp 与 wtmp 的语义差别是全章最关键的一对23:
| utmp | wtmp | |
|---|---|---|
| 回答 | 谁现在在线 | 历史上发生过什么(审计) |
| 登出时 | 抹掉登录记录 | 追加一条 DEAD_PROCESS |
| 更新方式 | 就地改写 | 只追加 |
utmpx API:六个函数
POSIX 标准的是带 x 的一套(getutxent 们),Linux 里带不带 x 完全等价;
非 Linux 系统老的 utmp API 已废弃24:
setutxent(); /* 回到文件头 */
while ( (u = getutxent()) != NULL ) /* 顺序取下一条 */
...
endutxent(); /* 关闭 */
utmpxname(WTMPX_FILE); /* 切换要读的文件(utmp 或 wtmp) */spl_utmpdump:把二进制文件变成人话
这个「转储器」程序用 utmpx API 顺序读出每条记录,打印类型/PID/用户/
终端/主机/时间。真实输出里能清楚看到事件的编年顺序:BOOT_TIME →
RUN_LVL → 各 USER_PROCESS → 对应的 DEAD_PROCESS → 下一次
BOOT_TIME……25 它同时是调试 last 仿写版的探针。
7. 主线例子第二程:spl_last——逆着时间走,括号配对的算法
问题的抽象:把登录登出看成括号串
删掉不相关字段后,wtmp 简化成一串事件。把「在 pts/N 登录」记作 [N、
「同一终端登出」记作 ]N,样本数据变成26:
[a [0 ]0 ]a [a ]a [1 [2 [4 [5 [6 ]5 ]6 ]4 ]1 ]2匹配规则:同一 N 的左右括号,越靠左的右括号配越靠左的左括号;
配对成功的 → 一次完整会话(有起止时间);配不上的左括号 → 「still
logged in」或「gone - no logout」。注意括号可以交错([1 [2 ]1 ]2),
栈解决不了(得弹出再压回),要用双向链表:倒着扫,遇到右括号存进
链表,遇到左括号去链表里找第一条同线右括号,找到就配对、删除、打印27。
倒着读文件:get_prev_utrec 的偏移芭蕾
last 从新到旧输出,所以文件要倒读。关键函数用两个 static 变量记住
状态:is_first(首调定位到 lseek(fd, -utsize, SEEK_END))和
saved_offset(下次要读的位置)28。每读完一条,文件偏移停在该条
之后,要读前一条得先 lseek(fd, -2*utsize, SEEK_CUR) 退两条;
saved_offset 减到 <0 就是文件头,收工29。
处理规则(逆时间视角)
| 遇到的记录 | 动作 |
|---|---|
DEAD_PROCESS(有 line) | 存入 saved_ut_recs 链表 |
USER_PROCESS | 链表找同 line 的登出:找到→打印起止+时长、删节点;没找到→看 last_shutdown_time:>0 打「gone - no logout」,==0 打「still logged in」 |
BOOT_TIME | 记录 last_boot_time;清空链表(更早的登录都属于上一个时代,不可能再配对了) |
SHUTDOWN_TIME(由 RUN_LVL + user=="shutdown" 识别,自定义类型值 32) | 记录 last_shutdown_time;打印时结束时间是下一次 boot 的时间;清空链表 |
图说:关机/重启是「时间纪元分界线」——分界线之前没配对成功的登出,
永远等不到配对了。 最后一行照原样打印 wtmp begins <第一条记录的时间>
30。
用户空间缓冲:留给习题的优化
一条一条 read 效率低(第 4 章的教训)。思路:一次读 NRECS 条进数组,
倒着从数组里取(索引从返回值-1 减到 0 再重新装载),文件偏移一次退
NRECS × UTSIZE31。接口设计成了四个函数(init_wtmp / load_buf /
get_next_utrec / wtmp_finalize)——「先设计接口再填实现」的示范。
8. 作者的判断与证据
有明证的:
- lastlog 的数组布局是从 man 页 CAVEATS + 稀疏性推理出来的, 推理链完整展示7。
- 「x86-64 兼容 32 位应用 ⇒ ll_time 是 int32_t」有
<bits/wordsize.h>的头文件原文佐证10。 - wtmp 登出记录「空用户名 ⇏ 登出」的单向蕴含,直接引 man 页原话21。
- spl_lastlog 与真 lastlog 的输出 diff 对照,差异定位到格式串,并追到 shadow-utils 源码18。
作者的判断(工程取舍):
- 「passwd 无序 UID 是否先排序再读 lastlog」——明确说本版不排序, 「生产版要研究这个问题」,留作习题31。
- 「spl_last 一次读一条、不做用户缓冲」——教学优先于性能,明说31。
- 「shutdown 的识别用 RUN_LVL+user=='shutdown'+line=='~'」—— 这是经验规则而非文档规范,作者自己也说「其他 runlevel 记录忽略」30。
9. 边界与局限
- utmp 格式机器相关(man 页两次警告),程序只能在编译它的架构上 读这些文件22。
- POSIX 只标准化 utmpx;Linux 的 utmp/utmpx 是同一结构,别的 Unix 上未必24。
- PAM、systemd-logind 等新式记账路径没展开——现代发行版里 utmp 的写入者已不只是 getty/login23。
- spl_last 不处理
-f指定别文件、用户/终端过滤等真命令的选项; wtmp 轮转(wtmp.1)不支持25。 - 用户缓冲方案只给了接口设计,实现留作习题31。
10. 可带走的
主线例子,一行复述: lseek(fd, 0, SEEK_END) 得文件大小 →
最高 UID = 大小/292 − 1 → lseek(fd, 292×uid, SEEK_SET) →
read 一条 292 字节记录 → ll_time==0 就是「Never logged in」。
- lseek 三基准两妙用:SEEK_SET/CUR/END;查当前位置、查文件大小。
- 偏移可以越过文件尾,写数据就造出「洞」;洞读作 0;
ls -l是逻辑 大小,du/ls -s才是磁盘占用——稀疏文件的核心认知。 - 系统文件路径用
<paths.h>宏(_PATH_LASTLOG 等),不硬编码。 - 跨架构的类型宽度陷阱:条件编译出的 int32_t/time_t 字段,必须 「赋值给 time_t 变量」再传给 localtime,不能强转指针。
- 读定长记录三查:
<0错、=sizeof成、其余不完整读。 - getpwuid 返回静态存储,要用先拷贝;getpwent 遍历到尾会设 ENOENT(man 页不写)。
- 两种读文件范式:显式 seek(精确、自担风险)vs API 迭代(省事、 跟随库);spl_lastlog 是混合体。
- utmp=谁现在在线(登出抹记录);wtmp=历史审计(登出追加)。
- last 的括号匹配:同线同配、左配最近左;栈不够(可交错),用双向 链表;倒读文件靠 static 记住偏移。
- BOOT/SHUTDOWN 是时间纪元分界线:遇到就清空未配对链表。
11. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| lseek 三基准与妙用 | Chapter 5: File I/O and Login Accounting | text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:67(搜「off_t lseek」) · :118(搜「current_pos」) |
| 文件空洞与 makefilehole | 同上 | :147(搜「NULL bytes」) · :211(搜「1000020」) · :220(搜「8192」) · :262(搜「od -a」) |
| 稀疏文件定义 | 同上 | :281(搜「sparse files」) |
| lastlog 布局推理 | 同上 | :404(搜「Large gaps」) · :420(搜「proportional to the user's user ID」) |
| struct lastlog 与条件类型 | 同上 | :504(搜「struct lastlog」) · :963(搜「typecast」) |
| paths.h 与头文件搜索 | 同上 | :328(搜「_PATH_WTMP」) · :477(搜「search starts here」) |
| 读结构体三查 | 同上 | :549(搜「READING STRUCTURES」) |
| /etc/passwd 七字段 | 同上 | :699(搜「linus:x:501」) |
| getpwuid/getpwent 与 ENOENT 坑 | 同上 | :712(搜「endpwent」) · :728(搜「static memory」) · :855(搜「ENOENT」) |
| spl_lastlog 程序 | 同上 | :1040(搜「spl_lastlog.c」) · :1077(搜「highest_uid」) |
| 输出对照与格式串考证 | 同上 | :1167(搜「%a %b %e %H:%M:%S %z %Y」) |
| 显式读 vs API 读 | 同上 | :1196(搜「explicit seeking」) |
| utmp 结构十种类型 | 同上 | :1403(搜「EMPTY」) · :1433(搜「struct utmp」) |
| utmp/wtmp 语义差别 | 同上 | :1558(搜「audit file」) · :1623(搜「essentially erased」) |
| utmpx API | 同上 | :1506(搜「obsolete」) · :1541(搜「endutxent」) |
| 空用户名的单向蕴含 | 同上 | :1874(搜「NULL username」) |
| spl_utmpdump 输出样本 | 同上 | :1828(搜「RUN_LVL」) |
| 括号抽象与链表算法 | 同上 | :1934(搜「[a」) · :1949(搜「squirrel」) · :1958(搜「stack」) |
| 倒读文件的偏移逻辑 | 同上 | :2165(搜「is_first」) · :2256(搜「2*utsize」) |
| spl_last 主循环规则 | 同上 | :2474(搜「switch」) |
| 用户空间缓冲设计 | 同上 | :2621(搜「NRECS」) |
Footnotes
-
出处:「Chapter 5: File I/O and Login Accounting」第 54–88 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:67,搜「off_t lseek」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 109–127 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:118,搜「current_pos」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 138–153 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:147,搜「NULL bytes」)与第 164–169 段的 makefilehole 示例。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 232–245 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:240,搜「999,424」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 257–277 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:262,搜「od -a」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 278–281 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:281,搜「sparse files」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 396–435 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:404,搜「Large gaps」) · :420(搜「proportional to the user's user ID」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 437–513 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:504,搜「struct lastlog」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 568–577 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:570,搜「user ID」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 952–974 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:963,搜「typecast」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 321–345 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:328,搜「_PATH_WTMP」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 455–486 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:477,搜「search starts here」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 549–566 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:549,搜「READING STRUCTURES」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 689–710 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:699,搜「linus:x:501」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 708–722 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:712,搜「endpwent」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 726–732 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:728,搜「static memory」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 843–861 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:855,搜「ENOENT」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 1146–1170 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1167,搜「%a %b %e %H:%M:%S %z %Y」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 1194–1214 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1196,搜「explicit seeking」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 1399–1424 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1403,搜「EMPTY」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 1871–1876 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1874,搜「NULL username」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 1431–1462 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1433,搜「struct utmp」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 1551–1560 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1558,搜「audit file」) · :1623(搜「essentially erased」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 1502–1549 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1506,搜「obsolete」) · :1541(搜「endutxent」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 1826–1859 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1828,搜「RUN_LVL」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 1884–1936 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1934,搜「[a」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 1937–1960 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:1949,搜「squirrel」) · :1958(搜「stack」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 2160–2210 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:2165,搜「is_first」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 2212–2270 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:2256,搜「2*utsize」)。 ↩ -
出处:「Chapter 5: File I/O and Login Accounting」第 2466–2549 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:2474,搜「switch」)。 ↩ ↩2 -
出处:「Chapter 5: File I/O and Login Accounting」第 2594–2658 段(
text/11-ch05-chapter-5-file-i-o-and-login-accounting.txt:2621,搜「NRECS」)。 ↩ ↩2 ↩3 ↩4