ext2文件在延迟删除后遇到系统断电会怎样

一、文件删除
通常在用户态我们删除一个文件都是通过rm命令来删除(删除文件夹的暂时就先不讨论了),这个删除操作并没有一个系统对应的rm系统调用,而是通过unlink系统调实现。在linux中,文件的删除不受文件是否正在被进程使用的限制,这一点和windows下的文件删除非常不同。这一点大家都知道,或者不知道也没有任何关系,因为这个行为通常不会对用户产生太大影响。但是一些linux进程对于只能运行一个实例的判断是通过简单的flock一个文件来实现的,假设说一个进程实例启动之后flock使用的文件被不小心删除了,此时第二进程实例就会启动,这一点对于假设系统只有一个实例运行的程序将会产生严重影响,例如对于一些共享类资源的使用会产生一些异常行为。
二、删除时ext2系统对dentry的删除
sys_unlink===>>>do_unlinkat===>>>vfs_unlink(struct inode *dir, struct dentry *dentry)===>>>ext2_unlink
err = ext2_delete_entry (de, page);
……
inode_dec_link_count(inode);
===>>>ext2_delete_entry
if (pde)
pde->rec_len = cpu_to_le16(to-from);
这里执行了最为简单的操作就是在将要删除的文件所在文件夹下找到<name, inode>数组,将被删除元素foo的前一个元素的reclen延长,延长到覆盖到被删除的元组长度。从这个操作执行的动作来看,我们也可以理解这里的"unlink"的意义,它可能是指将目录项中dentry描述的<name,inode>链接断开的意思。
tsecer@harry: touch foo
tsecer@harry: strace rm foo 2>&1 | tail 
mmap(NULL, 25406, PROT_READ, MAP_SHARED, 3, 0) = 0x7fce50de5000
close(3)                                = 0
futex(0x7fce50c05f20, FUTEX_WAKE, 2147483647) = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
lstat("foo", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
access("foo", W_OK)                     = 0
unlink("foo")                           = 0
close(1)                                = 0
exit_group(0)                           = ?
Process 2041 detached
在name和inode之间link断开之后,ext2文件系统执行了另一个操作,就是递减了name对应的inode结构的被引用计数i_nlink(通过inode_dec_link_count函数)。这个引用计数是文件系统的引用计数,这个引用计数通常是通过命令ln来增加参数对应文件的inode节点的nlink字段。例如当执行ln命令时,可以简单看下对应的系统调用:
tsecer@harry: strace rm foo 2>&1 | tail 
mmap(NULL, 25406, PROT_READ, MAP_SHARED, 3, 0) = 0x7fce50de5000
close(3)                                = 0
futex(0x7fce50c05f20, FUTEX_WAKE, 2147483647) = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
lstat("foo", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
access("foo", W_OK)                     = 0
unlink("foo")                           = 0
close(1)                                = 0
exit_group(0)                           = ?
Process 2041 detached
tsecer@harry: ll
total 0
tsecer@harry: touch foo
tsecer@harry: strace ln -sf foo bar 2>&1 | tail
fstat(3, {st_mode=S_IFREG|0644, st_size=25406, ...}) = 0
mmap(NULL, 25406, PROT_READ, MAP_SHARED, 3, 0) = 0x7fd222446000
close(3)                                = 0
futex(0x7fd222266f20, FUTEX_WAKE, 2147483647) = 0
stat("bar", 0x7fff5636b290)             = -1 ENOENT (No such file or directory)
lstat("bar", 0x7fff5636b0f0)            = -1 ENOENT (No such file or directory)
symlink("foo", "bar")                   = 0
close(1)                                = 0
exit_group(0)                           = ?
Process 6935 detached
tsecer@harry: strace ln -f foo bar 2>&1 | tail
futex(0x7f039bd8bf20, FUTEX_WAKE, 2147483647) = 0
stat("bar", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat("foo", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
lstat("bar", {st_mode=S_IFLNK|0777, st_size=3, ...}) = 0
link("foo", "bar")                      = -1 EEXIST (File exists)
unlink("bar")                           = 0
link("foo", "bar")                      = 0
close(1)                                = 0
exit_group(0)                           = ?
Process 6966 detached
tsecer@harry: 
可以看到,软链接和硬链接分别是通过symlink和link系统调用来实现连接。这个nlink和inode结构在内存中被引用次数不同,inode在内存中被引用的次数使用的是struct inode的i_count字段。
在ext2执行unlink的时候,可以注意到一点,ext“实时”断开了name到inode之间的链接,并且递减了name对应inode的文件系统引用计数i_nlink,但是文件系统本身并没有判断这个inode本身是否可以被删除。
三、删除时VFS的操作
vfs_unlink===>>>d_delete
if (atomic_read(&dentry->d_count) == 1) {
dentry_iput(dentry);
fsnotify_nameremove(dentry, isdir);
 
/* remove this and other inotify debug checks after 2.6.18 */
dentry->d_flags &= ~DCACHE_INOTIFY_PARENT_WATCHED;
return;
}
if (!d_unhashed(dentry))
__d_drop(dentry);
===>>>__d_drop
static inline void __d_drop(struct dentry *dentry)
{
if (!(dentry->d_flags & DCACHE_UNHASHED)) {
dentry->d_flags |= DCACHE_UNHASHED;
hlist_del_rcu(&dentry->d_hash);
}
}
在d_delete函数中,dentry的引用计数d_count并不能阻止对于__d_drop函数的调用,对应的用户可见的行为就是我们常见的多个进程打开文件并不影响文件被删除。这里只要进行一个dentry的删除操作,这地方都要执行__d_drop操作,在该函数中,调用了延迟的断链操作hlist_del_rcu(&dentry->d_hash),这个操作“几乎”是实时操作,也就是删除之后dentry将从d_hash所在链表中马上断开,那么这个d_hash连接的是什么链表呢?
同样是以ext2文件系统为例,在ext2_lookup===>>>d_splice_alias===>>>d_add===>>>d_rehash===>>>_d_rehash===>>>__d_rehash
static void __d_rehash(struct dentry * entry, struct hlist_head *list)
{
 
  entry->d_flags &= ~DCACHE_UNHASHED;
  hlist_add_head_rcu(&entry->d_hash, list);
}
简言之,这个是一个hash表,它将具有相同hash值的dentry链接在一起,和通常的hash一样,这个链表是为了加快查找速度,在d_lookup===>>>__d_lookup===>>>hlist_for_each_entry_rcu(dentry, node, head, d_hash)使用的就是这个链表,当执行了删除之后,通过dentry的hash查找已经无法找到该目录项。由于ext2文件系统对于dentry的删除在之前已经完成,所以在cache中找不到该目录项之后到具体文件系统中查找该目录项会同样失败,所以从用户的角度看这个文件从系统中消失了,无法通过文件系统找到该文件。
四、内存dentry结构在什么时候释放
sys_unlink===>>>do_unlinkat
dentry = lookup_hash(&nd);
……
error = vfs_unlink(nd.dentry->d_inode, dentry);
exit2:
dput(dentry);
在lookup_hash===>>>__lookup_hash===>>>cached_lookup===>>>d_lookup===>>>__d_lookup
if (!d_unhashed(dentry)) {
atomic_inc(&dentry->d_count);
found = dentry;
中,如果找到该目录项,在返回之前会递增该目录项的引用计数,然后在do_unlinkat删除之后执行了dput来递减该目录项的引用计数。
void dput(struct dentry *dentry)
{
if (atomic_read(&dentry->d_count) == 1)
might_sleep();
……
/* Unreachable? Get rid of it */
  if (d_unhashed(dentry))
goto kill_it;
   if (list_empty(&dentry->d_lru)) {
   dentry->d_flags |= DCACHE_REFERENCED;
   list_add(&dentry->d_lru, &dentry_unused);
   dentry_stat.nr_unused++;
   }
  spin_unlock(&dentry->d_lock);
spin_unlock(&dcache_lock);
return;
kill_it: {
struct dentry *parent;
……
/*drops the locks, at that point nobody can reach this dentry */
dentry_iput(dentry);
parent = dentry->d_parent;
d_free(dentry);
……
}
该目录项也就是在kill_it标签中的d_free函数调用处删除。
这里其实可以注意到dentry和inode一个很重要的区别:dentry没有文件系统应用技术的概念,它非常脆弱,任意一次删除都会导致该dentry从文件系统上消失,而inode只有当该inode的文件系统引用计数nlink为零的时候才会被删除
五、dentry对应的inode在文件系统(ext2)中的删除
dput===>>>dentry_iput===>>>iput===>>>iput_final===>>>generic_drop_inode
void generic_drop_inode(struct inode *inode)
{
if (!inode->i_nlink)
generic_delete_inode(inode);
else
generic_forget_inode(inode);
}
generic_delete_inode===>>>ext2_delete_inode===>>>ext2_free_inode
六、dentry对应的inode在内存中的删除
在generic_drop_inode函数的两个分支generic_delete_inode和generic_forget_inode函数的最后,都会执行destroy_inode(inode)函数来将该inode从系统内存中删除。
七、进程在用文件从磁盘删除后何时从文件系统(ext2为例)中删除及断电行为
1、删除在用文件
tsecer@harry: ps aux | grep tail
root      1973  0.0  0.0   6240   768 pts/3    S+   11:50   0:00 tail -f foo
root      2250  0.0  0.0   6344   892 pts/6    R+   11:51   0:00 grep tail
tsecer@harry: ll /proc/1973/fd
total 0
lrwx------ 1 root root 64 2016-10-20 11:50 0 -> /dev/pts/3
lrwx------ 1 root root 64 2016-10-20 11:50 1 -> /dev/pts/3
lrwx------ 1 root root 64 2016-10-20 11:50 2 -> /dev/pts/3
lr-x------ 1 root root 64 2016-10-20 11:50 3 -> /home/tsecer/rmfileinuse/foo (deleted)
tsecer@harry: 
这个(deleted)状态的判断也就是通过前面看到的dentry被删除时执行的
linux-2.6.21fsdcache.c:__d_path
if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
buflen -= 10;
end -= 10;
if (buflen < 0)
goto Elong;
memcpy(end, " (deleted)", 10);
}
2、inode及其对应的存储空间何时释放
从功能上看,dentry充当的只是一个索引的功能,就像内核文件中namei.c中的namei的命名一样,一个文件系统的dentry通常充当的是一个将字符串形式的name转换为文件系统的inode的过程。目录项的dentry更多的只是充当了一个map的索引功能,而真正的文件容器都是在inode中保存。为了保证inode可以被删除,需要满足两个条件,一个文件系统inode(ext2_inode)在操作系统VFS层中的映像struct inode的引用计数i_count数值为0,这个值为零表示系统中没有进程再引用这个结构,从而保证这个结构可以从内存中删除。满足这个条件之后需要进一步判断这个inode在文件系统中是否有被其它文件软链接或者硬链接过来,如果都没有,表示可以从文件系统上(这里通常是磁盘类的永久存储介质),也就是调用具体文件系统super_block的delete_inode功能。对于ext2文件系统,需要在这个接口中回收这个文件内容占用的磁盘空间,例如一个源代码文件中的所有代码就是这个代码的数据内容;然后回收这个文件系统格式化时静态分配的ext2_inode结构,这个结构中存储有文件的最后修改时间,权限,文件长度等我们通常在文件管理系统中可见的文件属性。
所以在inode结构的释放在iput函数中atomic_dec_and_lock(&inode->i_count, &inode_lock)判断如果该结构的内存引用计数为0的时候就可以从内存中删除。这里有一个有意思的地方,就是inode并没有像dentry一样在引用计数为0的时候先放入一个unused的LRU老化队列,而是通过destroy_inode直接从系统中删除该文件。其实这个可以从另一个方面来考虑这个问题,那就是在用户态程序操作文件的时候,通常在文件打开状态下是通过获得dentry结构的时候间接获得inode的引用计数。
从最为常见的open系统调用开始 sys_open===>>>do_sys_open===>>>do_filp_open===>>>open_namei===>>>path_lookup_open===>>>__path_lookup_intent_open===>>>do_path_lookup===>>>link_path_walk===>>>__link_path_walk===>>>do_lookup===>>>__d_lookup===>>>===>>>===>>>===>>>
if (!d_unhashed(dentry)) {
atomic_inc(&dentry->d_count);
found = dentry;
}
这个地方通过查找dentry的时候,在返回之前就递增了这个dentry的引用计数,注意:这里并没有递增这个dentry使用的inode的引用计数。
如果在内存的dcache中无法找到dentry,此时do_lookup会调用real_lookup函数来到具体的文件系统中查找dentry项:ext2_lookup===>>>iget===>>>iget_locked===>>>ifind_fast===>>>__iget
void __iget(struct inode * inode)
{
if (atomic_read(&inode->i_count)) {
atomic_inc(&inode->i_count);
return;
}
atomic_inc(&inode->i_count);
if (!(inode->i_state & (I_DIRTY|I_LOCK)))
list_move(&inode->i_list, &inode_in_use);
inodes_stat.nr_unused--;
}
此时递增了inode的引用计数。在open系统调用中do_sys_open===>>>do_filp_open===>>>nameidata_to_filp===>>>__dentry_open
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
int flags, struct file *f,
int (*open)(struct inode *, struct file *))
{
……
f->f_path.dentry = dentry;
……
最后在do_sys_open===>>>fd_install===>>>rcu_assign_pointer(fdt->fd[fd], file),将这个内核态中的struct file指针赋值到文件描述符表,并且struct file::path::dentry是为次次系统调用递增过引用计数的
在文件close的时候,sys_close===>>>filp_close===>>>fput===>>>__fput===>>>dput进入之前看到的流程,在fput函数中,还有(atomic_dec_and_test(&file->f_count))判断,这个通常是多线程下或者fork之后一个fd的引用计数增加的结果。在dput之后如果一个dentry的引用计数递减为0之后,在之后内存中的dcache回收的时候就有可能来回收掉这个dentry,shrink_dcache_sb===>>>prune_one_dentry===>>>dentry_iput===>>>iput
if (atomic_dec_and_lock(&inode->i_count, &inode_lock))
iput_final(inode);
相当于dcache是一个更低级的进位单位,当dcache的引用计数递减为0的时候可以递减一次它引用的inode的引用技术,inode引用计数递减为0之后才可以判断文件系统的引用计数nlink是否为0,如果nlink为0才可以从文件系统中删除
3、在该状态下断电会如何
从ext2_unlink函数来看,它不仅删除了name到inode的索引,而且递减了对应inode的nlink字段并标志该结构为dirty,这意味着在一定时间内操作系统会将这两个修改写回到磁盘,但是由于inode的内存引用数量非零,所以不会触发介质的delete_inode操作。如果在此时断电,很可能出现的情况是这个被删除的文件从文件系统中消失,但是这个文件使用的inode没有从super_block中删除,相当于inode泄漏,这些文件在e2fsck程序中就是认为的orphan节点。
八、简单验证下断电情况下
1、创建loopback设备
由于对于更新的ext系列文件系统,可能使用了日志之类的更高级属性,所以还是使用相对简单的ext2文件系统。我预安装的redhat版本使用的已经是ext4文件系统,所以使用loop设备创建一个ext2文件系统用来测试。
tsecer@harry: dd if=/dev/zero of=ext2.img  bs=1G count=1
1+0 records in
1+0 records out
1073741824 bytes (1.1 GB) copied, 1.89558 s, 566 MB/s
tsecer@harry: mkfs.ext2 ext2.img 
mke2fs 1.41.12 (17-May-2010)
ext2.img is not a block special device.
Proceed anyway? (y,n) y
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
65536 inodes, 262144 blocks
13107 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=268435456
8 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
32768, 98304, 163840, 229376
 
Writing inode tables: done                            
Writing superblocks and filesystem accounting information: done
 
This filesystem will be automatically checked every 22 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
tsecer@harry: mkdir ext2mnt
2、挂载文件并在使用态下删除文件
tsecer@harry: mount -o loop ext2.img ext2mnt/
tsecer@harry: echo this is a orphan test >ext2mnt/tsecer.txt 
tsecer@harry: cat ext2mnt/tsecer.txt
this is a orphan test
tsecer@harry: ll -i ext2mnt/tsecer.txt
13 -rw-r--r--. 1 root root 0 Oct 20 12:35 ext2mnt/tsecer.txt
tsecer@harry: debugfs  ext2.img 
debugfs 1.41.12 (17-May-2010)
debugfs:  stat tsecer.txt
Inode: 13   Type: regular    Mode:  0644   Flags: 0x0
Generation: 4073251085    Version: 0x00000000
User:     0   Group:     0   Size: 0
File ACL: 586    Directory ACL: 0
Links: 1   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x58091c98 -- Thu Oct 20 12:35:52 2016
atime: 0x58091c99 -- Thu Oct 20 12:35:53 2016
mtime: 0x58091c98 -- Thu Oct 20 12:35:52 2016
Size of extra inode fields: 0
BLOCKS:
 
debugfs:  blocks tsecer.txt
debugfs: Unknown request "blocks".  Type "?" for a request list.
debugfs:  quit
tsecer@harry: 
在debugfs的说明中有一个“blocks”命令可以列出文件所有的block,不过看来我现在用的版本并没有这个功能。
在一个终端中使用tail -f打开文件
tsecer@harry: tail -f ext2mnt/tsecer.txt
this is a orphan test
同时在另一个窗口中删除文件
tsecer@harry: rm ext2mnt/tsecer.txt 
rm: remove regular file `ext2mnt/tsecer.txt'? y
tsecer@harry: ps aux | grep tail
root      2852  0.0  0.0   4100   532 pts/0    S+   12:59   0:00 tail -f ext2mnt/tsecer.txt
root      2862  0.0  0.0   4352   720 pts/1    S+   13:00   0:00 grep tail
tsecer@harry: ll /proc/2852/fd
total 0
lrwx------. 1 root root 64 Oct 20 13:00 0 -> /dev/pts/0
lrwx------. 1 root root 64 Oct 20 13:00 1 -> /dev/pts/0
lrwx------. 1 root root 64 Oct 20 13:00 2 -> /dev/pts/0
lr-x------. 1 root root 64 Oct 20 13:00 3 -> /root/ext2mnt/tsecer.txt (deleted)
lr-x------. 1 root root 64 Oct 20 13:00 4 -> inotify
tsecer@harry: 
3、断点之后重启
tsecer@harry: e2fsck ext2.img -nv
e2fsck 1.41.12 (17-May-2010)
ext2.img was not cleanly unmounted, check forced.
Pass 1: Checking inodes, blocks, and sizes
Deleted inode 12 has zero dtime.  Fix? no
 
Deleted inode 13 has zero dtime.  Fix? no
 
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
Block bitmap differences:  -(585--586) -28672 -30720
Fix? no
 
Inode bitmap differences:  -(12--13)
Fix? no
 
 
ext2.img: ********** WARNING: Filesystem still has errors **********
 
这里看到有两个inode的不一致Inode bitmap differences:  -(12--13),也就是表示12号inode和13号inode。这里的12号inoe是由于我在执行这次实验之前已经执行过一次,所以12号inode和13一样处于相同的不一致状态。
4、通过e2fsprogs代码分析下输出的意义
e2fsprogs-1.43.3e2fsckproblem.c
static struct e2fsck_problem problem_table[] = {
……
/* Inode not used, but marked in bitmap */
{ PR_5_INODE_UNUSED,
  " -%i",
  PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
 
/* Inode used, but not marked used in bitmap */
{ PR_5_INODE_USED,
  " +%i",
  PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
……
/* Inode range not used, but marked in bitmap */
{ PR_5_INODE_RANGE_UNUSED,
  " -(%i--%j)",
  PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
 
/* Inode range used, but not marked used in bitmap */
{ PR_5_INODE_RANGE_USED,
  " +(%i--%j)",
  PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG },
由于输出中的前缀为‘-’,所以对应“Inode not used, but marked in bitmap”。这里e2fsprogs的代码我没仔细看,大致可以猜测它是通过扫描superblock的inode bitmap和inode结构数组中inode的i_links_count进行比较得出的结论。由于在unlink系统调用中已经递减了inode的i_links_count到0,但是由于内存inode的引用计数非零,所以inode本身及它占用的数据没有被删除,所以出现e2fsck中减少前缀的inode提示。
对于problem_table格式的解析在fix_problem===>>>print_e2fsck_message===>>>expand_percent_expression===>>>
static _INLINE_ void expand_percent_expression(FILE *f, ext2_filsys fs,
       char ch, int width, int *first,
       struct problem_context *ctx)
{
e2fsck_t e2fsck_ctx = fs ? (e2fsck_t) fs->priv_data : NULL;
const char *m;
 
if (!ctx)
goto no_context;
 
switch (ch) {
case '%':
fputc('%', f);
break;
case 'b':
#ifdef EXT2_NO_64_TYPE
fprintf(f, "%*u", width, (unsigned long) ctx->blk);
#else
fprintf(f, "%*llu", width, (unsigned long long) ctx->blk);
#endif
break;
case 'B':
if (ctx->blkcount == BLOCK_COUNT_IND)
m = _("indirect block");
else if (ctx->blkcount == BLOCK_COUNT_DIND)
m = _("double indirect block");
else if (ctx->blkcount == BLOCK_COUNT_TIND)
m = _("triple indirect block");
else if (ctx->blkcount == BLOCK_COUNT_TRANSLATOR)
m = _("translator block");
else
m = _("block #");
if (*first && islower(m[0]))
fputc(toupper(*m++), f);
fputs(m, f);
if (ctx->blkcount >= 0) {
#ifdef EXT2_NO_64_TYPE
fprintf(f, "%d", ctx->blkcount);
#else
fprintf(f, "%lld", (long long) ctx->blkcount);
#endif
}
break;
case 'c':
#ifdef EXT2_NO_64_TYPE
fprintf(f, "%*u", width, (unsigned long) ctx->blk2);
#else
fprintf(f, "%*llu", width, (unsigned long long) ctx->blk2);
#endif
break;
case 'd':
fprintf(f, "%*u", width, ctx->dir);
break;
case 'g':
fprintf(f, "%*u", width, ctx->group);
break;
case 'i':
fprintf(f, "%*u", width, ctx->ino);
break;
case 'j':
fprintf(f, "%*u", width, ctx->ino2);
break;
case 'm':
fprintf(f, "%*s", width, error_message(ctx->errcode));
break;
case 'N':
#ifdef EXT2_NO_64_TYPE
fprintf(f, "%*u", width, ctx->num);
#else
fprintf(f, "%*llu", width, (long long)ctx->num);
#endif
break;
case 'p':
print_pathname(f, fs, ctx->ino, 0);
break;
case 'P':
print_pathname(f, fs, ctx->ino2,
       ctx->dirent ? ctx->dirent->inode : 0);
break;
case 'q':
print_pathname(f, fs, ctx->dir, 0);
break;
case 'Q':
print_pathname(f, fs, ctx->dir, ctx->ino);
break;
case 'r':
#ifdef EXT2_NO_64_TYPE
fprintf(f, "%*d", width, ctx->blkcount);
#else
fprintf(f, "%*lld", width, (long long) ctx->blkcount);
#endif
break;
case 'S':
fprintf(f, "%llu", get_backup_sb(NULL, fs, NULL, NULL));
break;
case 's':
fprintf(f, "%*s", width, ctx->str ? ctx->str : "NULL");
break;
case 't':
print_time(f, (time_t) ctx->num);
break;
case 'T':
print_time(f, e2fsck_ctx ? e2fsck_ctx->now : time(0));
break;
case 'x':
fprintf(f, "0x%0*x", width, ctx->csum1);
break;
case 'X':
#ifdef EXT2_NO_64_TYPE
fprintf(f, "0x%0*x", width, ctx->num);
#else
fprintf(f, "0x%0*llx", width, (long long)ctx->num);
#endif
break;
case 'y':
fprintf(f, "0x%0*x", width, ctx->csum2);
break;
default:
no_context:
fprintf(f, "%%%c", ch);
break;
}
}
原文地址:https://www.cnblogs.com/tsecer/p/10487735.html