从 ls -U 到 Tomcat libs加载jar包:ext4 目录哈希序如何影响 Java 文件排序顺序

你贴出的源码其实串起了一条完整链路:Java File.list() → glibc readdirgetdents64 系统调用 → VFS iterate_dir → ext4 ext4_readdir 。这条链路决定了 ls -U 看到的顺序,也决定了 Tomcat 扫描 lib/WEB-INF/lib 下 jar 时底层枚举到的原始顺序。

先给结论:

  1. ls -u 小写是按访问时间排序,不是哈希序;ls -U 大写才是"不排序",直接按 readdir 返回顺序输出。
  2. 在 ext4 默认启用 dir_index 的现代系统上,readdir 返回的是 htree 目录哈希序
  3. Java File.list() 本身不排序,它拿到的就是 readdir 顺序。
  4. Tomcat 加载 lib 目录下 jar 时,通常会先 File.list(),然后 Arrays.sort(),所以最终类加载路径是 文件名字典序 ,而不是 ext4 哈希序。WEB-INF/lib 通常也会排序。若某处直接使用未排序的目录列表,才会暴露哈希序。

下面沿着你提供的源码逐层证明。

一、Java 层:File.list() 不排序

你贴出的 JDK 源码:

c

ini 复制代码
JNIEXPORT jobjectArray JNICALL
Java_java_io_UnixFileSystem_list(JNIEnv *env, jobject this,
                                 jobject file)
{
    DIR *dir = NULL;
    struct dirent *ptr;
    int len, maxlen;
    jobjectArray rv, old;
    jclass str_class;

    str_class = JNU_ClassString(env);
    CHECK_NULL_RETURN(str_class, NULL);

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        dir = opendir(path);
    } END_PLATFORM_STRING(env, path);
    if (dir == NULL) return NULL;

    len = 0;
    maxlen = 16;
    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
    if (rv == NULL) goto error;

    /* Scan the directory */
    while ((ptr = readdir(dir)) != NULL) {   // 【注释】直接按 readdir 返回顺序遍历
        jstring name;
        if (!strcmp(ptr->d_name, ".") || !strcmp(ptr->d_name, ".."))
            continue;
        if (len == maxlen) {
            old = rv;
            rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
            if (rv == NULL) goto error;
            if (JNU_CopyObjectArray(env, rv, old, len) < 0) goto error;
            (*env)->DeleteLocalRef(env, old);
        }
#ifdef MACOSX
        name = newStringPlatform(env, ptr->d_name);
#else
        name = JNU_NewStringPlatform(env, ptr->d_name);
#endif
        if (name == NULL) goto error;
        (*env)->SetObjectArrayElement(env, rv, len++, name); // 【注释】原样放入数组,没有排序
        (*env)->DeleteLocalRef(env, name);
    }
    closedir(dir);
    ...
}

关键点:while ((ptr = readdir(dir)) != NULL) 之后,直接 SetObjectArrayElementJava 没有做任何排序 。JDK 文档说 File.list() 不保证顺序,原因就在这里------它把底层顺序原封不动地暴露出来。

二、glibc 层:readdir 只是缓冲区搬运工

再看你贴出的 glibc 2.39 源码:

c

ini 复制代码
struct dirent64 *
__readdir64 (DIR *dirp)
{
  struct dirent64 *dp;
  int saved_errno = errno;

#if IS_IN (libc)
  __libc_lock_lock (dirp->lock);
#endif

  if (dirp->offset >= dirp->size)
    {
      /* We've emptied out our buffer.  Refill it.  */
      size_t maxread = dirp->allocation;
      ssize_t bytes;

      bytes = __getdents64 (dirp->fd, dirp->data, maxread); // 【注释】缓冲区空才向内核要一批目录项
      if (bytes <= 0)
	{
	  if (bytes == 0 || errno == ENOENT)
	    __set_errno (saved_errno);
#if IS_IN (libc)
	  __libc_lock_unlock (dirp->lock);
#endif
	  return NULL;
	}
      dirp->size = (size_t) bytes;
      dirp->offset = 0;
    }

  dp = (struct dirent64 *) &dirp->data[dirp->offset];
  dirp->offset += dp->d_reclen; // 【注释】按内核返回的字节流顺序逐个切分
  dirp->filepos = dp->d_off;

#if IS_IN (libc)
  __libc_lock_unlock (dirp->lock);
#endif

  return dp;
}

readdir 每次调用只是从 dirp->data 缓冲区里取出一个 dirent64。缓冲区里的内容来自 __getdents64,而 __getdents64 只是发起系统调用:

c

arduino 复制代码
ssize_t
__getdents64 (int fd, void *buf, size_t nbytes)
{
  if (nbytes > INT_MAX)
    nbytes = INT_MAX;
  return INLINE_SYSCALL_CALL (getdents64, fd, buf, nbytes); // 【注释】进入内核
}

glibc 不会重排目录项。顺序在内核返回数据时已经固定。

三、内核系统调用层:交给 VFS

你贴出的 getdents64 系统调用:

c

ini 复制代码
SYSCALL_DEFINE3(getdents64, unsigned int, fd,
		struct linux_dirent64 __user *, dirent, unsigned int, count)
{
	struct fd f;
	struct getdents_callback64 buf = {
		.ctx.actor = filldir64,
		.count = count,
		.current_dir = dirent
	};
	int error;

	f = fdget_pos(fd);
	if (!f.file)
		return -EBADF;
	...
	error = iterate_dir(f.file, &buf.ctx); // 【注释】交给 VFS 的 iterate_dir
	...
}

系统调用本身没有排序逻辑,它把"遍历目录"的请求转给 VFS。

四、VFS 层:调用具体文件系统的 iterate_shared

你贴出的 iterate_dir

c

ini 复制代码
int iterate_dir(struct file *file, struct dir_context *ctx)
{
	struct inode *inode = file_inode(file);
	...
	int res = -ENOTDIR;

	if (!file->f_op->iterate_shared)
		goto out;

	res = security_file_permission(file, MAY_READ);
	if (res)
		goto out;

	res = fsnotify_file_perm(file, MAY_READ);
	if (res)
		goto out;

	res = down_read_killable(&inode->i_rwsem);
	if (res)
		goto out;

	res = -ENOENT;
	if (!IS_DEADDIR(inode)) {
		ctx->pos = file->f_pos;
		res = file->f_op->iterate_shared(file, ctx); // 【注释】调用具体文件系统的 iterate_shared
		file->f_pos = ctx->pos;
		fsnotify_access(file);
		file_accessed(file);
	}
	inode_unlock_shared(inode);
out:
	return res;
}
EXPORT_SYMBOL(iterate_dir);

VFS 只是路由。对于 ext4 目录,file->f_op 是:

c

ini 复制代码
const struct file_operations ext4_dir_operations = {
	.llseek		= ext4_dir_llseek,
	.read		= generic_read_dir,
	.iterate_shared	= ext4_readdir, // 【注释】ext4 目录遍历入口
	.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext4_compat_ioctl,
#endif
	.fsync		= ext4_sync_file,
	.release	= ext4_release_dir,
};

所以最终进入 ext4_readdir

五、ext4 层:htree 决定哈希序

你贴出的 ext4_readdir 关键片段:

c

scss 复制代码
static int ext4_readdir(struct file *file, struct dir_context *ctx)
{
	...
	if (is_dx_dir(inode)) {
		err = ext4_dx_readdir(file, ctx); // 【注释】目录有 htree 索引时走哈希树遍历
		if (err != ERR_BAD_DX_DIR)
			return err;
		...
	}

	if (ext4_has_inline_data(inode)) {
		int has_inline_data = 1;
		err = ext4_read_inline_dir(file, ctx, &has_inline_data);
		if (has_inline_data)
			return err;
	}
	...
	while (ctx->pos < inode->i_size) {
		...
		while (ctx->pos < inode->i_size
		       && offset < sb->s_blocksize) {
			de = (struct ext4_dir_entry_2 *) (bh->b_data + offset);
			...
			offset += ext4_rec_len_from_disk(de->rec_len,
					sb->s_blocksize);
			if (le32_to_cpu(de->inode)) {
				pr_debug("ext4_readdir: found %.*s\n", de->name_len, de->name);
				if (!IS_ENCRYPTED(inode)) {
					if (!dir_emit(ctx, de->name,
					    de->name_len,
					    le32_to_cpu(de->inode),
					    get_dtype(sb, de->file_type))) // 【注释】按当前块内目录项顺序输出
						goto done;
				} else {
					...
				}
			}
			ctx->pos += ext4_rec_len_from_disk(de->rec_len,
						sb->s_blocksize);
		}
		...
	}
done:
	err = 0;
errout:
	fscrypt_fname_free_buffer(&fstr);
	brelse(bh);
	return err;
}

关键分支是:

c

scss 复制代码
if (is_dx_dir(inode)) {
    err = ext4_dx_readdir(file, ctx); // 【注释】哈希树遍历
    if (err != ERR_BAD_DX_DIR)
        return err;
}

现代 Linux 发行版格式化 ext4 时默认启用 dir_index。因此大多数目录都走 ext4_dx_readdir,遍历的是目录的 B 树索引。叶块中的目录项按文件名哈希值 排列,所以 readdir 返回的是哈希序。

如果目录很小、没有启用 htree,或者 htree 损坏回退,才会走线性扫描。这时顺序大致是创建顺序,但删除文件后新文件可能填入空洞,顺序就不再稳定。

六、ls -U 和 Tomcat libs 的顺序

ls -U 是"不排序",直接输出 readdir 顺序。所以在 ext4 htree 目录上,你看到的就是哈希序。默认 ls 看起来是字母序,是因为 ls 自己在用户态排序。

Tomcat 加载 lib 目录下 jar 时,底层同样要先枚举目录。Tomcat 的 common.loader 默认包含 ${catalina.home}/lib/*.jar。解析通配符时,Tomcat 会调用 File.list() 获取目录项。你贴出的 Java_java_io_UnixFileSystem_list 已经证明:File.list() 本身返回的是 readdir 顺序,不排序。所以 Tomcat 拿到的原始数组在 ext4 htree 下就是哈希序。

但 Tomcat 通常不会直接依赖这个原始顺序。ClassLoaderFactory 在展开 *.jar 时,一般会:

java

scss 复制代码
String[] files = dir.list();   // 底层是 ext4 哈希序
Arrays.sort(files);            // Tomcat 显式排序
for (String file : files) {
    if (file.endsWith(".jar")) {
        // 按字典序加入类加载路径
    }
}

因此 Tomcat lib 下 jar 的最终查找顺序是 文件名字典序WEB-INF/lib 下 jar 通常也会在 WebappClassLoaderBase 启动时对 resources.list("/WEB-INF/lib") 结果排序后加入。Tomcat 这样做是为了可重复性,避免不同机器、不同 ext4 目录哈希布局导致类加载顺序漂移。

所以完整答案是:

  • 底层 File.list() / readdir:ext4 htree 下是哈希序;
  • Tomcat lib 加载 :Tomcat 自己 Arrays.sort(),所以是字典序;
  • 若某处未排序:就会暴露 ext4 哈希序。

实践上不要依赖 jar 扫描顺序来消解同名类冲突。需要稳定顺序时,显式排序;需要避免冲突时,不要放同名类,或使用更明确的类加载隔离策略。 #源码

scss 复制代码
@Override
    public native String[] list(File f);
    
 JNIEXPORT jobjectArray JNICALL
Java_java_io_UnixFileSystem_list(JNIEnv *env, jobject this,
                                 jobject file)
{
    DIR *dir = NULL;
    struct dirent *ptr;
    int len, maxlen;
    jobjectArray rv, old;
    jclass str_class;

    str_class = JNU_ClassString(env);
    CHECK_NULL_RETURN(str_class, NULL);

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        dir = opendir(path);
    } END_PLATFORM_STRING(env, path);
    if (dir == NULL) return NULL;

    /* Allocate an initial String array */
    len = 0;
    maxlen = 16;
    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
    if (rv == NULL) goto error;

    /* Scan the directory */
    while ((ptr = readdir(dir)) != NULL) {
        jstring name;
        if (!strcmp(ptr->d_name, ".") || !strcmp(ptr->d_name, ".."))
            continue;
        if (len == maxlen) {
            old = rv;
            rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
            if (rv == NULL) goto error;
            if (JNU_CopyObjectArray(env, rv, old, len) < 0) goto error;
            (*env)->DeleteLocalRef(env, old);
        }
#ifdef MACOSX
        name = newStringPlatform(env, ptr->d_name);
#else
        name = JNU_NewStringPlatform(env, ptr->d_name);
#endif
        if (name == NULL) goto error;
        (*env)->SetObjectArrayElement(env, rv, len++, name);
        (*env)->DeleteLocalRef(env, name);
    }
    closedir(dir);

    /* Copy the final results into an appropriately-sized array */
    if (len < maxlen) {
        old = rv;
        rv = (*env)->NewObjectArray(env, len, str_class, NULL);
        if (rv == NULL) {
            return NULL;
        }
        if (JNU_CopyObjectArray(env, rv, old, len) < 0) {
            return NULL;
        }
    }
    return rv;

 error:
    closedir(dir);
    return NULL;
}   

/* Read a directory entry from DIRP.  */
struct dirent64 *
__readdir64 (DIR *dirp)
{
  struct dirent64 *dp;
  int saved_errno = errno;

#if IS_IN (libc)
  __libc_lock_lock (dirp->lock);
#endif

  if (dirp->offset >= dirp->size)
    {
      /* We've emptied out our buffer.  Refill it.  */

      size_t maxread = dirp->allocation;
      ssize_t bytes;

      bytes = __getdents64 (dirp->fd, dirp->data, maxread);
      if (bytes <= 0)
	{
	  /* Linux may fail with ENOENT on some file systems if the
	     directory inode is marked as dead (deleted).  POSIX
	     treats this as a regular end-of-directory condition, so
	     do not set errno in that case, to indicate success.  */
	  if (bytes == 0 || errno == ENOENT)
	    __set_errno (saved_errno);
#if IS_IN (libc)
	  __libc_lock_unlock (dirp->lock);
#endif
	  return NULL;
	}
      dirp->size = (size_t) bytes;

      /* Reset the offset into the buffer.  */
      dirp->offset = 0;
    }

  dp = (struct dirent64 *) &dirp->data[dirp->offset];
  dirp->offset += dp->d_reclen;
  dirp->filepos = dp->d_off;

#if IS_IN (libc)
  __libc_lock_unlock (dirp->lock);
#endif

  return dp;
}
libc_hidden_def (__readdir64)

#if _DIRENT_MATCHES_DIRENT64
strong_alias (__readdir64, __readdir)
weak_alias (__readdir64, readdir64)
weak_alias (__readdir64, readdir)

/* The kernel struct linux_dirent64 matches the 'struct dirent64' type.  */
ssize_t
__getdents64 (int fd, void *buf, size_t nbytes)
{
  /* The system call takes an unsigned int argument, and some length
     checks in the kernel use an int type.  */
  if (nbytes > INT_MAX)
    nbytes = INT_MAX;
  return INLINE_SYSCALL_CALL (getdents64, fd, buf, nbytes);
}
libc_hidden_def (__getdents64)
weak_alias (__getdents64, getdents64)


SYSCALL_DEFINE3(getdents64, unsigned int, fd,
		struct linux_dirent64 __user *, dirent, unsigned int, count)
{
	struct fd f;
	struct getdents_callback64 buf = {
		.ctx.actor = filldir64,
		.count = count,
		.current_dir = dirent
	};
	int error;

	f = fdget_pos(fd);
	if (!f.file)
		return -EBADF;
	// yym-gaizao
	// ========== 添加打印路径的代码 ==========
	char *path_buf = kmalloc(PATH_MAX, GFP_KERNEL);
	if (path_buf) {
		char *path = d_path(&(f.file)->f_path, path_buf, PATH_MAX);
		if (!IS_ERR(path))
			pr_debug("getdents64: file fd=%d, name: %s\n", fd, path);
		else
			pr_debug("getdents64: file fd=%d (d_path error)\n", fd);
		kfree(path_buf);
	} else {
		pr_debug("getdents64: file fd=%d (no memory for path)\n", fd);
	}
	error = iterate_dir(f.file, &buf.ctx);
	if (error >= 0)
		error = buf.error;
	if (buf.prev_reclen) {
		struct linux_dirent64 __user * lastdirent;
		typeof(lastdirent->d_off) d_off = buf.ctx.pos;

		lastdirent = (void __user *) buf.current_dir - buf.prev_reclen;
		if (put_user(d_off, &lastdirent->d_off))
			error = -EFAULT;
		else
			error = count - buf.count;
	}
	fdput_pos(f);
	return error;
}

int iterate_dir(struct file *file, struct dir_context *ctx)
{
	struct inode *inode = file_inode(file);
	// yym-gaizao
	// ========== 添加打印路径的代码 ==========
	char *path_buf = kmalloc(PATH_MAX, GFP_KERNEL);
	if (path_buf) {
		char *path = d_path(&file->f_path, path_buf, PATH_MAX);
		if (!IS_ERR(path))
			pr_debug("iterate_dir:  %s, ino=%lu\n", path, inode->i_ino);
		else
			pr_debug("iterate_dir: (error), ino=%lu\n", inode->i_ino);
		kfree(path_buf);
	} else {
		pr_debug("iterate_dir:  (no memory for path)\n");
	}
	int res = -ENOTDIR;

	if (!file->f_op->iterate_shared)
		goto out;

	res = security_file_permission(file, MAY_READ);
	if (res)
		goto out;

	res = fsnotify_file_perm(file, MAY_READ);
	if (res)
		goto out;

	res = down_read_killable(&inode->i_rwsem);
	if (res)
		goto out;

	res = -ENOENT;
	if (!IS_DEADDIR(inode)) {
		ctx->pos = file->f_pos;
		res = file->f_op->iterate_shared(file, ctx);
		file->f_pos = ctx->pos;
		fsnotify_access(file);
		file_accessed(file);
	}
	inode_unlock_shared(inode);
out:
	return res;
}
EXPORT_SYMBOL(iterate_dir);

const struct file_operations ext4_dir_operations = {
	.llseek		= ext4_dir_llseek,
	.read		= generic_read_dir,
	.iterate_shared	= ext4_readdir,
	.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= ext4_compat_ioctl,
#endif
	.fsync		= ext4_sync_file,
	.release	= ext4_release_dir,
};

static int ext4_readdir(struct file *file, struct dir_context *ctx)
{
	unsigned int offset;
	int i;
	struct ext4_dir_entry_2 *de;
	int err;
	struct inode *inode = file_inode(file);
	struct super_block *sb = inode->i_sb;
	struct buffer_head *bh = NULL;
	struct fscrypt_str fstr = FSTR_INIT(NULL, 0);

	err = fscrypt_prepare_readdir(inode);
	if (err)
		return err;

	if (is_dx_dir(inode)) {
		err = ext4_dx_readdir(file, ctx);
		if (err != ERR_BAD_DX_DIR)
			return err;

		/* Can we just clear INDEX flag to ignore htree information? */
		if (!ext4_has_metadata_csum(sb)) {
			/*
			 * We don't set the inode dirty flag since it's not
			 * critical that it gets flushed back to the disk.
			 */
			ext4_clear_inode_flag(inode, EXT4_INODE_INDEX);
		}
	}

	if (ext4_has_inline_data(inode)) {
		int has_inline_data = 1;
		err = ext4_read_inline_dir(file, ctx,
					   &has_inline_data);
		if (has_inline_data)
			return err;
	}

	if (IS_ENCRYPTED(inode)) {
		err = fscrypt_fname_alloc_buffer(EXT4_NAME_LEN, &fstr);
		if (err < 0)
			return err;
	}

	while (ctx->pos < inode->i_size) {
		struct ext4_map_blocks map;

		if (fatal_signal_pending(current)) {
			err = -ERESTARTSYS;
			goto errout;
		}
		cond_resched();
		offset = ctx->pos & (sb->s_blocksize - 1);
		map.m_lblk = ctx->pos >> EXT4_BLOCK_SIZE_BITS(sb);
		map.m_len = 1;
		err = ext4_map_blocks(NULL, inode, &map, 0);
		if (err == 0) {
			/* m_len should never be zero but let's avoid
			 * an infinite loop if it somehow is */
			if (map.m_len == 0)
				map.m_len = 1;
			ctx->pos += map.m_len * sb->s_blocksize;
			continue;
		}
		if (err > 0) {
			pgoff_t index = map.m_pblk >>
					(PAGE_SHIFT - inode->i_blkbits);
			if (!ra_has_index(&file->f_ra, index))
				page_cache_sync_readahead(
					sb->s_bdev->bd_inode->i_mapping,
					&file->f_ra, file,
					index, 1);
			file->f_ra.prev_pos = (loff_t)index << PAGE_SHIFT;
			bh = ext4_bread(NULL, inode, map.m_lblk, 0);
			if (IS_ERR(bh)) {
				err = PTR_ERR(bh);
				bh = NULL;
				goto errout;
			}
		}

		if (!bh) {
			/* corrupt size?  Maybe no more blocks to read */
			if (ctx->pos > inode->i_blocks << 9)
				break;
			ctx->pos += sb->s_blocksize - offset;
			continue;
		}

		/* Check the checksum */
		if (!buffer_verified(bh) &&
		    !ext4_dirblock_csum_verify(inode, bh)) {
			EXT4_ERROR_FILE(file, 0, "directory fails checksum "
					"at offset %llu",
					(unsigned long long)ctx->pos);
			ctx->pos += sb->s_blocksize - offset;
			brelse(bh);
			bh = NULL;
			continue;
		}
		set_buffer_verified(bh);

		/* If the dir block has changed since the last call to
		 * readdir(2), then we might be pointing to an invalid
		 * dirent right now.  Scan from the start of the block
		 * to make sure. */
		if (!inode_eq_iversion(inode, file->f_version)) {
			for (i = 0; i < sb->s_blocksize && i < offset; ) {
				de = (struct ext4_dir_entry_2 *)
					(bh->b_data + i);
				/* It's too expensive to do a full
				 * dirent test each time round this
				 * loop, but we do have to test at
				 * least that it is non-zero.  A
				 * failure will be detected in the
				 * dirent test below. */
				if (ext4_rec_len_from_disk(de->rec_len,
					sb->s_blocksize) < ext4_dir_rec_len(1,
									inode))
					break;
				i += ext4_rec_len_from_disk(de->rec_len,
							    sb->s_blocksize);
			}
			offset = i;
			ctx->pos = (ctx->pos & ~(sb->s_blocksize - 1))
				| offset;
			file->f_version = inode_query_iversion(inode);
		}

		while (ctx->pos < inode->i_size
		       && offset < sb->s_blocksize) {
			de = (struct ext4_dir_entry_2 *) (bh->b_data + offset);
			if (ext4_check_dir_entry(inode, file, de, bh,
						 bh->b_data, bh->b_size,
						 offset)) {
				/*
				 * On error, skip to the next block
				 */
				ctx->pos = (ctx->pos |
						(sb->s_blocksize - 1)) + 1;
				break;
			}
			offset += ext4_rec_len_from_disk(de->rec_len,
					sb->s_blocksize);
			if (le32_to_cpu(de->inode)) {
				//yym-gaizao
				pr_debug("ext4_readdir: found %.*s\n", de->name_len, de->name);
				if (!IS_ENCRYPTED(inode)) {
					if (!dir_emit(ctx, de->name,
					    de->name_len,
					    le32_to_cpu(de->inode),
					    get_dtype(sb, de->file_type)))
						goto done;
				} else {
					int save_len = fstr.len;
					struct fscrypt_str de_name =
							FSTR_INIT(de->name,
								de->name_len);

					/* Directory is encrypted */
					err = fscrypt_fname_disk_to_usr(inode,
						EXT4_DIRENT_HASH(de),
						EXT4_DIRENT_MINOR_HASH(de),
						&de_name, &fstr);
					de_name = fstr;
					fstr.len = save_len;
					if (err)
						goto errout;
					if (!dir_emit(ctx,
					    de_name.name, de_name.len,
					    le32_to_cpu(de->inode),
					    get_dtype(sb, de->file_type)))
						goto done;
				}
			}
			ctx->pos += ext4_rec_len_from_disk(de->rec_len,
						sb->s_blocksize);
		}
		if ((ctx->pos < inode->i_size) && !dir_relax_shared(inode))
			goto done;
		brelse(bh);
		bh = NULL;
	}
done:
	err = 0;
errout:
	fscrypt_fname_free_buffer(&fstr);
	brelse(bh);
	return err;
}
相关推荐
qeen872 小时前
【Linux】操作系统之进程介绍(一)
linux·笔记·学习·进程
邪修king2 小时前
Re:Linux系统篇(二十五):文件系统(一):磁盘硬件底层原理:从物理结构到 CHS/LBA 寻址,搞懂硬盘数据的定位逻辑
java·linux·运维·gpt
小则又沐风a2 小时前
TCP协议讲解-----了解TCP可靠性的基石
linux
刚入门的大一新生2 小时前
Linux-简单设计libc库
linux·c++
科小墨2 小时前
【Linux 开发者系列 · 第 1 节】NPM 完全指南:它是什么、为什么重要、在 Linux 上怎么用
linux
IT北辰3 小时前
Linux运维:sed命令修改配置文件IP
linux
刘天远3 小时前
Agent最小权限实现:策略表、临时令牌与撤权回归
java·jvm·人工智能·sqlite
牢姐与蒯3 小时前
Linux文件(三).Ext系列文件系统
linux·运维·服务器
2401_869769593 小时前
linux基本指令2
linux