一:前言

在用户空间中,建立目录所用的API为mkdir().它在内核中的系统调用入口是sys_mkdir().今天跟踪一下

函数来分析linux文件系统中目录的建立过程.

二:sys_mkdir()

Sys_mkdir()对应的代码如下:

asmlinkage long sys_mkdir(const char __user * pathname, int mode)

{

int error = 0;

char * tmp;

//把用户空间的值copy到内核空间

tmp = getname(pathname);

error = PTR_ERR(tmp);

if (!IS_ERR(tmp)) {

struct dentry *dentry;

struct nameidata nd;

//先查到它的父目录,看父目录是否存在

error = path_lookup(tmp, LOOKUP_PARENT, &nd);

if (error)

goto out;

//寻找子结点的dentry. 如果没有,则新建之

dentry = lookup_create(&nd, 1);

error = PTR_ERR(dentry);

if (!IS_ERR(dentry)) {

if (!IS_POSIXACL(nd.dentry->d_inode))

mode &= ~current->fs->umask;

//与具体的文件系统相关的部份

error = vfs_mkdir(nd.dentry->d_inode, dentry, mode);

//减少dentry的引用计数

dput(dentry);

}

up(&nd.dentry->d_inode->i_sem);

//释放临时内存

path_release(&nd);

out:

putname(tmp);

}

return error;

}

这个函数里面有几个重要的子函数. path_lookup()在前一篇文章中已经分析过了.如果不太了解,请参阅相关的部份.

lookup_create()的代码如下:

{

struct dentry *dentry;

//防止并发操作,获得信号量

down(&nd->dentry->d_inode->i_sem);

dentry = ERR_PTR(-EEXIST);

//如果之前的查找过程失败

if (nd->last_type != LAST_NORM)

goto fail;

//去掉LOOKUP_PARENT标志

nd->flags &= ~LOOKUP_PARENT;

//在缓存中寻找相应的dentry.如果没有。则新建之

dentry = lookup_hash(&nd->last, nd->dentry);

//创建或者查找失败

if (IS_ERR(dentry))

goto fail;

//如果不是建立一个目录而且文件名字不是以0结尾

//出错退出

if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode)

goto enoent;

return dentry;

enoent:

dput(dentry);

dentry = ERR_PTR(-ENOENT);

fail:

return dentry;

}

lookup_hash()à __lookup_hash():

static struct dentry * __lookup_hash(struct qstr *name, struct dentry * base, struct nameidata *nd)

{

struct dentry * dentry;

struct inode *inode;

int err;

inode = base->d_inode;

//检查是否有相关的权限

err = permission(inode, MAY_EXEC, nd);

dentry = ERR_PTR(err);

if (err)

goto out;

/*

* See if the low-level filesystem might want

* to use its own hash..

*/

//如果自定义了hash计算

if (base->d_op && base->d_op->d_hash) {

err = base->d_op->d_hash(base, name);

dentry = ERR_PTR(err);

if (err < 0)

goto out;

}

//从缓存中寻找

dentry = cached_lookup(base, name, nd);

if (!dentry) {

//如果缓存中没有相关项。则新建之

struct dentry *new = d_alloc(base, name);

dentry = ERR_PTR(-ENOMEM);

if (!new)

goto out;

//到具体的文件系统中查找

dentry = inode->i_op->lookup(inode, new, nd);

if (!dentry)

dentry = new;

else

dput(new);

}

out:

return dentry;

}

值得注意的是:经过上述的操作,返回的dentry有可能是原本就存在的.对这种情况是怎么排除的呢?继续看sys_mkdir()的另一个子函数:

int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)

{

//对异常情况的排除和权限的检查

int error = may_create(dir, dentry, NULL);

if (error)

return error;

//如果父结点不允许mkdir操作

if (!dir->i_op || !dir->i_op->mkdir)

return -EPERM;

mode &= (S_IRWXUGO|S_ISVTX);

error = security_inode_mkdir(dir, dentry, mode);

if (error)

return error;

DQUOT_INIT(dir);

//调用父结点的mkdir操作

error = dir->i_op->mkdir(dir, dentry, mode);

if (!error) {

//如果成功,通告与之关联的进程

inode_dir_notify(dir, DN_CREATE);

security_inode_post_mkdir(dir,dentry, mode);

}

return error;

}

在这里看到,最终会调用父进程的i_op.mkdir操作.另外,对于上面说的相应结点已经存在的情况是在may_create()中检测的:

static inline int may_create(struct inode *dir, struct dentry *child,

struct nameidata *nd)

{

//如果欲建结点的inode已经存在

//对于一个新建的dentry.其d_inode指向为空.

if (child->d_inode)

return -EEXIST;

//判断父目录是否已经失效

if (IS_DEADDIR(dir))

return -ENOENT;

//权限检查

return permission(dir,MAY_WRITE | MAY_EXEC, nd);

}

Mkdir的大体架构就如此了.下面讨论一下rootfs和ext2中的目录创建.

三:rootfs的目录创建

在前一篇文章分析到.挂载rootfs时,对文件系统根目录的inode.i_op赋值如下:

static struct inode_operations ramfs_dir_inode_operations = {

.create       = ramfs_create,

.lookup       = simple_lookup,

.link         = simple_link,

.unlink       = simple_unlink,

.symlink = ramfs_symlink,

.mkdir        = ramfs_mkdir,

.rmdir        = simple_rmdir,

.mknod        = ramfs_mknod,

.rename       = simple_rename,

};

对应的mkdir操作入口是ramfs_mkdir():

static int ramfs_mkdir(struct inode * dir, struct dentry * dentry, int mode)

{

//创建结点

int retval = ramfs_mknod(dir, dentry, mode | S_IFDIR, 0);

//如果创建成功,更新i_nlink计数

if (!retval)

dir->i_nlink++;

return retval;

}

Ramsf_mknod()的代码如下:

static int

ramfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)

{

//在文件系统中分其为配一个inode

struct inode * inode = ramfs_get_inode(dir->i_sb, mode, dev);

int error = -ENOSPC;

if (inode) {

//如果分配成功

if (dir->i_mode & S_ISGID) {

inode->i_gid = dir->i_gid;

if (S_ISDIR(mode))

inode->i_mode |= S_ISGID;

}

//将dentry与分配的inode关联起来

d_instantiate(dentry, inode);

//增加dentry的引用计数

dget(dentry); /* Extra count - pin the dentry in core */

error = 0;

}

return error;

}

这个函数中的子函数我们都在前面已经分析过.请自行查阅本站的其它文档.其操作非常简单。就是分配一个inode。然后将inode 与dentry建立关联.因为rootfs是一个基于RAM的文件系统。其inode的分配就是在内存中创建一个inode空间,然后为其各项操作赋值而已.

四:ext2中的目录创建

经过上一章的分析可以看到.ext2文件系统根目录的inode.i_op被赋值为ext2_dir_inode_operations.其结构如下所示:

struct inode_operations ext2_dir_inode_operations = {

.create       = ext2_create,

.lookup       = ext2_lookup,

.link         = ext2_link,

.unlink       = ext2_unlink,

.symlink = ext2_symlink,

.mkdir        = ext2_mkdir,

.rmdir        = ext2_rmdir,

.mknod        = ext2_mknod,

.rename       = ext2_rename,

#ifdef CONFIG_EXT2_FS_XATTR

.setxattr = generic_setxattr,

.getxattr = generic_getxattr,

.listxattr    = ext2_listxattr,

.removexattr  = generic_removexattr,

#endif

.setattr = ext2_setattr,

.permission   = ext2_permission,

}

Mkdir对应的入口为ext2_mkdir().代码如下:

static int ext2_mkdir(struct inode * dir, struct dentry * dentry, int mode)

{

struct inode * inode;

int err = -EMLINK;

if (dir->i_nlink >= EXT2_LINK_MAX)

goto out;

//增加dir的引用计数,并将其置为"脏"

ext2_inc_count(dir);

//在文件系统中分配一个inode

inode = ext2_new_inode (dir, S_IFDIR | mode);

err = PTR_ERR(inode);

if (IS_ERR(inode))

goto out_dir;

//为inode的各项操作赋值

inode->i_op = &ext2_dir_inode_operations;

inode->i_fop = &ext2_dir_operations;

//为inode对应的i_mapping赋值

if (test_opt(inode->i_sb, NOBH))

inode->i_mapping->a_ops = &ext2_nobh_aops;

else

inode->i_mapping->a_ops = &ext2_aops;

//增加inode的引用计数,并将其置为"脏"

ext2_inc_count(inode);

//对目录结点的初始化

err = ext2_make_empty(inode, dir);

if (err)

goto out_fail;

//更新父目录,使inode加入父目录

err = ext2_add_link(dentry, inode);

if (err)

goto out_fail;

//使dentry和inode建立关联

d_instantiate(dentry, inode);

out:

return err;

out_fail:

ext2_dec_count(inode);

ext2_dec_count(inode);

iput(inode);

out_dir:

ext2_dec_count(dir);

goto out;

}

逐个分析上面所涉及到的子函数.

在ext2中分配一个inode是由ext2_new_inode()完成的.它的代码如下:

struct inode *ext2_new_inode(struct inode *dir, int mode)

{

struct super_block *sb;

struct buffer_head *bitmap_bh = NULL;

struct buffer_head *bh2;

int group, i;

ino_t ino = 0;

struct inode * inode;

struct ext2_group_desc *gdp;

struct ext2_super_block *es;

struct ext2_inode_info *ei;

struct ext2_sb_info *sbi;

int err;

sb = dir->i_sb;

//分配一个inode

inode = new_inode(sb);

if (!inode)

return ERR_PTR(-ENOMEM);

//inode的私有结构

ei = EXT2_I(inode);

//super_block中的ext2私有结构

sbi = EXT2_SB(sb);

//ext2的super_block

es = sbi->s_es;

//寻找一个合适的组来分配inode

if (S_ISDIR(mode)) {

if (test_opt(sb, OLDALLOC))

group = find_group_dir(sb, dir);

else

group = find_group_orlov(sb, dir);

} else

group = find_group_other(sb, dir);

if (group == -1) {

err = -ENOSPC;

goto fail;

}

//遍历组描述符

for (i = 0; i < sbi->s_groups_count; i++) {

//group对应的组开始遍历

//取得组描述符

gdp = ext2_get_group_desc(sb, group, &bh2);

//释放bitmap_bh.已经后面会使用这个临时变量

brelse(bitmap_bh);

//取得组描述符里的inode位图

bitmap_bh = read_inode_bitmap(sb, group);

if (!bitmap_bh) {

err = -EIO;

goto fail;

}

ino = 0;

repeat_in_this_group:

//寻找位图中第一个没有使用的位

ino = ext2_find_next_zero_bit((unsigned long *)bitmap_bh->b_data,

EXT2_INODES_PER_GROUP(sb), ino);

//如果找到的位大于块组中的inode数.那从group之后的块组中分配

if (ino >= EXT2_INODES_PER_GROUP(sb)) {

/*

* Rare race: find_group_xx() decided that there were

* free inodes in this group, but by the time we tried

* to allocate one, they're all gone.  This can also

* occur because the counters which find_group_orlov()

* uses are approximate.  So just go and search the

* next block group.

*/

//已经到达块组数目最大值。则将其置为零.然后重新循环

if (++group == sbi->s_groups_count)

group = 0;

continue;

}

//将inode 位图中的分配位置位

if (ext2_set_bit_atomic(sb_bgl_lock(sbi, group),

ino, bitmap_bh->b_data)) {

/* we lost this inode */

//如果该位已经被置位了.说明其它的内核控制路径将其分配了.

//那就找它的下一个没有被使用的inode

//如果下一个超过了这个组中的最大inode数目。那从下一个块组中分配

if (++ino >= EXT2_INODES_PER_GROUP(sb)) {

/* this group is exhausted, try next group */

if (++group == sbi->s_groups_count)

group = 0;

continue;

}

/* try to find free inode in the same group */

//重新从块组中寻找没有被使用的inode

goto repeat_in_this_group;

}

//如果运行到这里的话,说明分配成功了

goto got;

}

/*

* Scanned all blockgroups.

*/

err = -ENOSPC;

goto fail;

got:

mark_buffer_dirty(bitmap_bh);

if (sb->s_flags & MS_SYNCHRONOUS)

sync_dirty_buffer(bitmap_bh);

brelse(bitmap_bh);

//将块组中的inode序号转换为全局inode计数

ino += group * EXT2_INODES_PER_GROUP(sb) + 1;

//如果inode序号小于super_block的超始inode序号或者大于inode总数

//出错退出

if (ino < EXT2_FIRST_INO(sb) || ino > le32_to_cpu(es->s_inodes_count)) {

ext2_error (sb, "ext2_new_inode",

"reserved inode or inode > inodes count - "

"block_group = %d,inode=%lu", group,

(unsigned long) ino);

err = -EIO;

goto fail;

}

//更新统计计数

percpu_counter_mod(&sbi->s_freeinodes_counter, -1);

if (S_ISDIR(mode))

percpu_counter_inc(&sbi->s_dirs_counter);

spin_lock(sb_bgl_lock(sbi, group));

gdp->bg_free_inodes_count =

cpu_to_le16(le16_to_cpu(gdp->bg_free_inodes_count) - 1);

//更新s_debts

if (S_ISDIR(mode)) {

if (sbi->s_debts[group] < 255)

sbi->s_debts[group]++;

gdp->bg_used_dirs_count =

cpu_to_le16(le16_to_cpu(gdp->bg_used_dirs_count) + 1);

} else {

if (sbi->s_debts[group])

sbi->s_debts[group]--;

}

spin_unlock(sb_bgl_lock(sbi, group));

sb->s_dirt = 1;

mark_buffer_dirty(bh2);

inode->i_uid = current->fsuid;

if (test_opt (sb, GRPID))

inode->i_gid = dir->i_gid;

else if (dir->i_mode & S_ISGID) {

inode->i_gid = dir->i_gid;

if (S_ISDIR(mode))

mode |= S_ISGID;

} else

inode->i_gid = current->fsgid;

inode->i_mode = mode;

//更新inode表示的索引结点号

inode->i_ino = ino;

inode->i_blksize = PAGE_SIZE;    /* This is the optimal IO size (for stat), not the fs block size */

inode->i_blocks = 0;

//使i_mtine,i_atime,i_ctime置为当前时间

inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;

memset(ei->i_data, 0, sizeof(ei->i_data));

ei->i_flags = EXT2_I(dir)->i_flags & ~EXT2_BTREE_FL;

if (S_ISLNK(mode))

ei->i_flags &= ~(EXT2_IMMUTABLE_FL|EXT2_APPEND_FL);

/* dirsync is only applied to directories */

if (!S_ISDIR(mode))

ei->i_flags &= ~EXT2_DIRSYNC_FL;

ei->i_faddr = 0;

ei->i_frag_no = 0;

ei->i_frag_size = 0;

ei->i_file_acl = 0;

ei->i_dir_acl = 0;

ei->i_dtime = 0;

ei->i_block_group = group;

ei->i_next_alloc_block = 0;

ei->i_next_alloc_goal = 0;

ei->i_prealloc_block = 0;

ei->i_prealloc_count = 0;

ei->i_dir_start_lookup = 0;

ei->i_state = EXT2_STATE_NEW;

ext2_set_inode_flags(inode);

spin_lock(&sbi->s_next_gen_lock);

inode->i_generation = sbi->s_next_generation++;

spin_unlock(&sbi->s_next_gen_lock);

insert_inode_hash(inode);

if (DQUOT_ALLOC_INODE(inode)) {

DQUOT_DROP(inode);

err = -ENOSPC;

goto fail2;

}

err = ext2_init_acl(inode, dir);

if (err) {

DQUOT_FREE_INODE(inode);

goto fail2;

}

//置inode为“脏”

mark_inode_dirty(inode);

ext2_debug("allocating inode %lu\n", inode->i_ino);

ext2_preread_inode(inode);

return inode;

fail2:

inode->i_flags |= S_NOQUOTA;

inode->i_nlink = 0;

iput(inode);

return ERR_PTR(err);

fail:

make_bad_inode(inode);

iput(inode);

return ERR_PTR(err);

}

查找一个末使用的索引结点有一个规则,就是尽量使每个块组达到平衡.所以linux在ext2_sb_info结构中加了一个s_debts字段.用来表示每个块组中的文件与目录的分配情况.计算的方法是在此find_group_orlov(目录)和find_group_other(其它类型的文件)中完成的.

每个页面都包含两个特殊目录结构 “.”和 “..”.单点代表其本身,双点代表父目录.这个过程是在ext2_make_empty()中完成的.对应代码如下:

int ext2_make_empty(struct inode *inode, struct inode *parent)

{

struct address_space *mapping = inode->i_mapping;

//找到页面映射所代表的首个页面

struct page *page = grab_cache_page(mapping, 0);

unsigned chunk_size = ext2_chunk_size(inode);

struct ext2_dir_entry_2 * de;

int err;

void *kaddr;

if (!page)

return -ENOMEM;

//先调用prepare_write().因为之后会将page写到文件系统中去

err = mapping->a_ops->prepare_write(NULL, page, 0, chunk_size);

if (err) {

unlock_page(page);

goto fail;

}

//将page临时映射到内核

kaddr = kmap_atomic(page, KM_USER0);

//目录中的第一个文件对象

de = (struct ext2_dir_entry_2 *)kaddr;

//每个目录中都有两个默认存在的对象.和..

//将'.'加至目录中,其inode结点号指向其本身

de->name_len = 1;

de->rec_len = cpu_to_le16(EXT2_DIR_REC_LEN(1));

memcpy (de->name, ".\0\0", 4);

de->inode = cpu_to_le32(inode->i_ino);

ext2_set_de_type (de, inode);

//设置'..'.使其指向父目录

de = (struct ext2_dir_entry_2 *)(kaddr + EXT2_DIR_REC_LEN(1));

de->name_len = 2;

de->rec_len = cpu_to_le16(chunk_size - EXT2_DIR_REC_LEN(1));

de->inode = cpu_to_le32(parent->i_ino);

memcpy (de->name, "..\0", 4);

ext2_set_de_type (de, inode);

//释放掉映射区间

kunmap_atomic(kaddr, KM_USER0);

//将更改的页面提交到文件系统

err = ext2_commit_chunk(page, 0, chunk_size);

fail:

//页面使用完了,减少其使用计数

page_cache_release(page);

return err;

}

初始完成之后,要将子目录插入父目录所表的空间的。它是由ext2_add_link()完成的。代码如下:

int ext2_add_link (struct dentry *dentry, struct inode *inode)

{

//得到父目录的inode

struct inode *dir = dentry->d_parent->d_inode;

const char *name = dentry->d_name.name;

int namelen = dentry->d_name.len;

unsigned chunk_size = ext2_chunk_size(dir);

unsigned reclen = EXT2_DIR_REC_LEN(namelen);

unsigned short rec_len, name_len;

struct page *page = NULL;

ext2_dirent * de;

//父目录结点大小所占的页面数

unsigned long npages = dir_pages(dir);

unsigned long n;

char *kaddr;

unsigned from, to;

int err;

/*

* We take care of directory expansion in the same loop.

* This code plays outside i_size, so it locks the page

* to protect that region.

*/

//遍历结点所在的空间

for (n = 0; n <= npages; n++) {

char *dir_end;

page = ext2_get_page(dir, n);

err = PTR_ERR(page);

if (IS_ERR(page))

goto out;

lock_page(page);

kaddr = page_address(page);

//本页面的最后的位置.

//ext2_last_byte: 如果剩余的长度大于一个页面,则返回一个页面大小.否则返回剩余空间大小

dir_end = kaddr + ext2_last_byte(dir, n);

de = (ext2_dirent *)kaddr;

kaddr += PAGE_CACHE_SIZE - reclen;

while ((char *)de <= kaddr) {

//到了结点空间的末尾

if ((char *)de == dir_end) {

/* We hit i_size */

name_len = 0;

rec_len = chunk_size;

de->rec_len = cpu_to_le16(chunk_size);

de->inode = 0;

goto got_it;

}

//目录中文件所占空间长度为0.非法

if (de->rec_len == 0) {

ext2_error(dir->i_sb, __FUNCTION__,

"zero-length directory entry");

err = -EIO;

goto out_unlock;

}

err = -EEXIST;

//在目录所包含的文件中,含有同名的结点

if (ext2_match (namelen, name, de))

goto out_unlock;

name_len = EXT2_DIR_REC_LEN(de->name_len);

rec_len = le16_to_cpu(de->rec_len);

//de->inode==0.表示目录中的此结点被删除

//rec_len >= reclen:表示旧结点中有足够的空间存储新的结点

if (!de->inode && rec_len >= reclen)

goto got_it;

//这个结点中有空间剩余.(可能是它后面有节点被删除造成的)

if (rec_len >= name_len + reclen)

goto got_it;

de = (ext2_dirent *) ((char *) de + rec_len);

}

unlock_page(page);

ext2_put_page(page);

}

BUG();

return -EINVAL;

got_it:

from = (char*)de - (char*)page_address(page);

to = from + rec_len;

err = page->mapping->a_ops->prepare_write(NULL, page, from, to);

if (err)

goto out_unlock;

if (de->inode) {

//这是属于结点空间有剩余的情况

//即在空间中插入一个新的结点

ext2_dirent *de1 = (ext2_dirent *) ((char *) de + name_len);

de1->rec_len = cpu_to_le16(rec_len - name_len);

de->rec_len = cpu_to_le16(name_len);

de = de1;

}

//对目录的相关项进行赋值

de->name_len = namelen;

memcpy (de->name, name, namelen);

de->inode = cpu_to_le32(inode->i_ino);

ext2_set_de_type (de, inode);

//提交所做的修改,将其写入文件系统

err = ext2_commit_chunk(page, from, to);

//更改时间戳

dir->i_mtime = dir->i_ctime = CURRENT_TIME;

EXT2_I(dir)->i_flags &= ~EXT2_BTREE_FL;

mark_inode_dirty(dir);

/* OFFSET_CACHE */

out_put:

ext2_put_page(page);

out:

return err;

out_unlock:

unlock_page(page);

goto out_put;

}

在这里,忽略了页面映射与文件系统驱动的交互过程。关于页面缓存后续再给出章节进行分析.

五:小结

在这一节里,以rootfs和ext2文件系统为例分析了目录的建立过程.只要对ext2文件系统的相关部分有所了解.理解这部份代码并不难.其中关于页面缓存部份以后再给出专题分析.详情请关注本站更新.

linux创建根目录代码,Linux文件系统之目录的建立相关推荐

  1. linux 如何赋值目录,Linux文件系统之目录的建立

    一:前言 在用户空间中,建立目录所用的API为mkdir().它在内核中的系统调用入口是sys_mkdir().今天跟踪一下 函数来分析linux文件系统中目录的建立过程. 二:sys_mkdir() ...

  2. linux 创建新用户时没有用户目录

    linux 创建新用户时没有用户目录 **问题:**使用 useradd -d /home/user02 user02 创建新用户并指定目录后,用 ls /home 无法看到 user02 的目录. ...

  3. linux创建用户并修改用户家目录

    ubuntu 1.首先用adduser添加用户,但是用户的目录是adduser默认的目录 adduser name 2.创建你想要的用户目录 mkdir name 3.vi /etc/passwd 更 ...

  4. 在linux下载github代码,linux 定时下载github最新代码

    场景:网站的代码在github上托管,静态网站部署在服务器上,每次自己修改完本地代码后,提交到github上,需要自己去服务器上执行git pull 拉取最新代码, 为了解决这种操作,自己再服务器上  ...

  5. linux创建文件系统方法,linux创建文件系统方法

    fdisk用法:用于硬盘分区 mkfs用法:在硬盘分区上创建文件系统 mkfs [ -V ] [ -t fstype ] [ fs-options ] filesys [ blocks ] 创建一个L ...

  6. linux上mkfs代码,Linux mkfs使用

    mkfs命令是 makefilesystem的缩写,用来在特定的分区建立Linux文件系统 常见的文件系统有 FAT 在Win 9X下,FAT16支持的分区最大为2GB.我们知道计算机将信息保存在硬盘 ...

  7. linux简单重定向代码,Linux Shell编程(26)——代码块重定向

    像 while, until, 和 for 循环代码块, 甚至 if/then 测试结构的代码块都能做到标准输入的重定向. 即使函数也可以使用这种重定向的格式 .所有的这些依靠代码块结尾的 < ...

  8. linux创建定时任务命令,linux下添加定时任务

    linux下添加定时任务 linux下添加定时任务 ### linux下定时执行任务的方法 在LINUX中你应该先输入crontab -e,然后就会有个vi编辑界面,再输入0 3 * * 1 /cle ...

  9. linux创建sftp用户并指定访问目录,linux – 创建SFTP用户只能访问一个目录.

    我更喜欢创建用户组sftp,并将该组中的用户限制在其主目录中. 首先,编辑你的/ etc / ssh / sshd_config文件并将其添加到底部. Match Group sftp ChrootD ...

最新文章

  1. Python多线程(3)——Queue模块
  2. AngularJS鼠标进入划出事件
  3. ImageMagick convert
  4. 观察者模式及Java实现例子
  5. Ubuntu MySQL 配置 ip binding
  6. 日常视频一秒变游戏,人物可以随意操控:全靠Facebook的实时算法
  7. python客户端服务器_Python客户端和服务器ch
  8. cc2530设计性实验代码五
  9. FTP服务器vsftpd配置详解
  10. 关于42步进电机驱动——基于STM32 HAL库实现
  11. VMware虚拟机全屏状态下如何快速切换回原主机界面
  12. python三阶魔方_三阶魔方自动求解及动态可视化matlab代码
  13. Ethereum/mist项目下Ethereum Wallet和Mist的区别
  14. 工作篇 之 解决谷歌地图与 NestedScrollView 滑动冲突
  15. 计算机科学博士点,全国计算机博士点排名(全国前70名)
  16. Python数字图像处理---1.1图像的像素格式与图像读写
  17. 不重启JVM动态添加日志(阿里Arthas)
  18. 华为设备链路聚合(LACP模式)
  19. 穷举算法——奶牛碑文(cow)
  20. app小程序手机端Python爬虫实战07UiSelector通过resourceId、层级定位方式

热门文章

  1. 通过PEB遍历进程模块(x64/wow4)
  2. virtualenv
  3. JQuery语法 JQuery对象与原生对象互转 文档就绪函数与window.onload的区别
  4. 04:输出保留3位小数的浮点数
  5. 【识记】Nginx 访问控制
  6. 大数据之_数据采集Flume_架构---Flume工作笔记004
  7. 深度学习工作笔记002---曼哈顿距离_欧氏距离
  8. springcache使用笔记002_注释驱动的 Spring cache 按条件查询
  9. Maven异常总结001---Maven project导入到myeclipse时候出现异常:could not get mojo execution paramater value
  10. fckeditor异常总结---The server didn‘t reply with a proper XML data. Please check your configuration.