diff options
-rw-r--r-- | fs/sysv/dir.c | 154 | ||||
-rw-r--r-- | fs/sysv/namei.c | 42 | ||||
-rw-r--r-- | fs/sysv/sysv.h | 3 |
3 files changed, 111 insertions, 88 deletions
diff --git a/fs/sysv/dir.c b/fs/sysv/dir.c index 88e38cd8f5c9..999bceb99974 100644 --- a/fs/sysv/dir.c +++ b/fs/sysv/dir.c @@ -28,37 +28,50 @@ const struct file_operations sysv_dir_operations = { .fsync = generic_file_fsync, }; -static inline void dir_put_page(struct page *page) +inline void dir_put_page(struct page *page, void *page_addr) { - kunmap(page); + kunmap_local((void *)((unsigned long)page_addr & PAGE_MASK)); put_page(page); } -static int dir_commit_chunk(struct page *page, loff_t pos, unsigned len) +static void dir_commit_chunk(struct page *page, loff_t pos, unsigned len) { struct address_space *mapping = page->mapping; struct inode *dir = mapping->host; - int err = 0; block_write_end(NULL, mapping, pos, len, len, page, NULL); if (pos+len > dir->i_size) { i_size_write(dir, pos+len); mark_inode_dirty(dir); } - if (IS_DIRSYNC(dir)) - err = write_one_page(page); - else - unlock_page(page); + unlock_page(page); +} + +static int sysv_handle_dirsync(struct inode *dir) +{ + int err; + + err = filemap_write_and_wait(dir->i_mapping); + if (!err) + err = sync_inode_metadata(dir, 1); return err; } -static struct page * dir_get_page(struct inode *dir, unsigned long n) +/* + * Calls to dir_get_page()/dir_put_page() must be nested according to the + * rules documented in mm/highmem.rst. + * + * NOTE: sysv_find_entry() and sysv_dotdot() act as calls to dir_get_page() + * and must be treated accordingly for nesting purposes. + */ +static void *dir_get_page(struct inode *dir, unsigned long n, struct page **p) { struct address_space *mapping = dir->i_mapping; struct page *page = read_mapping_page(mapping, n, NULL); - if (!IS_ERR(page)) - kmap(page); - return page; + if (IS_ERR(page)) + return ERR_CAST(page); + *p = page; + return kmap_local_page(page); } static int sysv_readdir(struct file *file, struct dir_context *ctx) @@ -80,11 +93,11 @@ static int sysv_readdir(struct file *file, struct dir_context *ctx) for ( ; n < npages; n++, offset = 0) { char *kaddr, *limit; struct sysv_dir_entry *de; - struct page *page = dir_get_page(inode, n); + struct page *page; - if (IS_ERR(page)) + kaddr = dir_get_page(inode, n, &page); + if (IS_ERR(kaddr)) continue; - kaddr = (char *)page_address(page); de = (struct sysv_dir_entry *)(kaddr+offset); limit = kaddr + PAGE_SIZE - SYSV_DIRSIZE; for ( ;(char*)de <= limit; de++, ctx->pos += sizeof(*de)) { @@ -96,11 +109,11 @@ static int sysv_readdir(struct file *file, struct dir_context *ctx) if (!dir_emit(ctx, name, strnlen(name,SYSV_NAMELEN), fs16_to_cpu(SYSV_SB(sb), de->inode), DT_UNKNOWN)) { - dir_put_page(page); + dir_put_page(page, kaddr); return 0; } } - dir_put_page(page); + dir_put_page(page, kaddr); } return 0; } @@ -123,6 +136,11 @@ static inline int namecompare(int len, int maxlen, * returns the cache buffer in which the entry was found, and the entry * itself (as a parameter - res_dir). It does NOT read the inode of the * entry - you'll have to do that yourself if you want to. + * + * On Success dir_put_page() should be called on *res_page. + * + * sysv_find_entry() acts as a call to dir_get_page() and must be treated + * accordingly for nesting purposes. */ struct sysv_dir_entry *sysv_find_entry(struct dentry *dentry, struct page **res_page) { @@ -142,11 +160,10 @@ struct sysv_dir_entry *sysv_find_entry(struct dentry *dentry, struct page **res_ n = start; do { - char *kaddr; - page = dir_get_page(dir, n); - if (!IS_ERR(page)) { - kaddr = (char*)page_address(page); - de = (struct sysv_dir_entry *) kaddr; + char *kaddr = dir_get_page(dir, n, &page); + + if (!IS_ERR(kaddr)) { + de = (struct sysv_dir_entry *)kaddr; kaddr += PAGE_SIZE - SYSV_DIRSIZE; for ( ; (char *) de <= kaddr ; de++) { if (!de->inode) @@ -155,7 +172,7 @@ struct sysv_dir_entry *sysv_find_entry(struct dentry *dentry, struct page **res_ name, de->name)) goto found; } - dir_put_page(page); + dir_put_page(page, kaddr); } if (++n >= npages) @@ -185,11 +202,9 @@ int sysv_add_link(struct dentry *dentry, struct inode *inode) /* We take care of directory expansion in the same loop */ for (n = 0; n <= npages; n++) { - page = dir_get_page(dir, n); - err = PTR_ERR(page); - if (IS_ERR(page)) - goto out; - kaddr = (char*)page_address(page); + kaddr = dir_get_page(dir, n, &page); + if (IS_ERR(kaddr)) + return PTR_ERR(kaddr); de = (struct sysv_dir_entry *)kaddr; kaddr += PAGE_SIZE - SYSV_DIRSIZE; while ((char *)de <= kaddr) { @@ -200,14 +215,13 @@ int sysv_add_link(struct dentry *dentry, struct inode *inode) goto out_page; de++; } - dir_put_page(page); + dir_put_page(page, kaddr); } BUG(); return -EINVAL; got_it: - pos = page_offset(page) + - (char*)de - (char*)page_address(page); + pos = page_offset(page) + offset_in_page(de); lock_page(page); err = sysv_prepare_chunk(page, pos, SYSV_DIRSIZE); if (err) @@ -215,12 +229,12 @@ got_it: memcpy (de->name, name, namelen); memset (de->name + namelen, 0, SYSV_DIRSIZE - namelen - 2); de->inode = cpu_to_fs16(SYSV_SB(inode->i_sb), inode->i_ino); - err = dir_commit_chunk(page, pos, SYSV_DIRSIZE); + dir_commit_chunk(page, pos, SYSV_DIRSIZE); dir->i_mtime = dir->i_ctime = current_time(dir); mark_inode_dirty(dir); + err = sysv_handle_dirsync(dir); out_page: - dir_put_page(page); -out: + dir_put_page(page, kaddr); return err; out_unlock: unlock_page(page); @@ -230,19 +244,20 @@ out_unlock: int sysv_delete_entry(struct sysv_dir_entry *de, struct page *page) { struct inode *inode = page->mapping->host; - char *kaddr = (char*)page_address(page); - loff_t pos = page_offset(page) + (char *)de - kaddr; + loff_t pos = page_offset(page) + offset_in_page(de); int err; lock_page(page); err = sysv_prepare_chunk(page, pos, SYSV_DIRSIZE); - BUG_ON(err); + if (err) { + unlock_page(page); + return err; + } de->inode = 0; - err = dir_commit_chunk(page, pos, SYSV_DIRSIZE); - dir_put_page(page); + dir_commit_chunk(page, pos, SYSV_DIRSIZE); inode->i_ctime = inode->i_mtime = current_time(inode); mark_inode_dirty(inode); - return err; + return sysv_handle_dirsync(inode); } int sysv_make_empty(struct inode *inode, struct inode *dir) @@ -259,9 +274,7 @@ int sysv_make_empty(struct inode *inode, struct inode *dir) unlock_page(page); goto fail; } - kmap(page); - - base = (char*)page_address(page); + base = kmap_local_page(page); memset(base, 0, PAGE_SIZE); de = (struct sysv_dir_entry *) base; @@ -271,8 +284,9 @@ int sysv_make_empty(struct inode *inode, struct inode *dir) de->inode = cpu_to_fs16(SYSV_SB(inode->i_sb), dir->i_ino); strcpy(de->name,".."); - kunmap(page); - err = dir_commit_chunk(page, 0, 2 * SYSV_DIRSIZE); + kunmap_local(base); + dir_commit_chunk(page, 0, 2 * SYSV_DIRSIZE); + err = sysv_handle_dirsync(inode); fail: put_page(page); return err; @@ -286,16 +300,15 @@ int sysv_empty_dir(struct inode * inode) struct super_block *sb = inode->i_sb; struct page *page = NULL; unsigned long i, npages = dir_pages(inode); + char *kaddr; for (i = 0; i < npages; i++) { - char *kaddr; - struct sysv_dir_entry * de; - page = dir_get_page(inode, i); + struct sysv_dir_entry *de; - if (IS_ERR(page)) + kaddr = dir_get_page(inode, i, &page); + if (IS_ERR(kaddr)) continue; - kaddr = (char *)page_address(page); de = (struct sysv_dir_entry *)kaddr; kaddr += PAGE_SIZE-SYSV_DIRSIZE; @@ -314,44 +327,51 @@ int sysv_empty_dir(struct inode * inode) if (de->name[1] != '.' || de->name[2]) goto not_empty; } - dir_put_page(page); + dir_put_page(page, kaddr); } return 1; not_empty: - dir_put_page(page); + dir_put_page(page, kaddr); return 0; } /* Releases the page */ -void sysv_set_link(struct sysv_dir_entry *de, struct page *page, +int sysv_set_link(struct sysv_dir_entry *de, struct page *page, struct inode *inode) { struct inode *dir = page->mapping->host; - loff_t pos = page_offset(page) + - (char *)de-(char*)page_address(page); + loff_t pos = page_offset(page) + offset_in_page(de); int err; lock_page(page); err = sysv_prepare_chunk(page, pos, SYSV_DIRSIZE); - BUG_ON(err); + if (err) { + unlock_page(page); + return err; + } de->inode = cpu_to_fs16(SYSV_SB(inode->i_sb), inode->i_ino); - err = dir_commit_chunk(page, pos, SYSV_DIRSIZE); - dir_put_page(page); + dir_commit_chunk(page, pos, SYSV_DIRSIZE); dir->i_mtime = dir->i_ctime = current_time(dir); mark_inode_dirty(dir); + return sysv_handle_dirsync(inode); } -struct sysv_dir_entry * sysv_dotdot (struct inode *dir, struct page **p) +/* + * Calls to dir_get_page()/dir_put_page() must be nested according to the + * rules documented in mm/highmem.rst. + * + * sysv_dotdot() acts as a call to dir_get_page() and must be treated + * accordingly for nesting purposes. + */ +struct sysv_dir_entry *sysv_dotdot(struct inode *dir, struct page **p) { - struct page *page = dir_get_page(dir, 0); - struct sysv_dir_entry *de = NULL; + struct sysv_dir_entry *de = dir_get_page(dir, 0, p); - if (!IS_ERR(page)) { - de = (struct sysv_dir_entry*) page_address(page) + 1; - *p = page; - } - return de; + if (IS_ERR(de)) + return NULL; + /* ".." is the second directory entry */ + return de + 1; } ino_t sysv_inode_by_name(struct dentry *dentry) @@ -362,7 +382,7 @@ ino_t sysv_inode_by_name(struct dentry *dentry) if (de) { res = fs16_to_cpu(SYSV_SB(dentry->d_sb), de->inode); - dir_put_page(page); + dir_put_page(page, de); } return res; } diff --git a/fs/sysv/namei.c b/fs/sysv/namei.c index ecd424461511..a25862773d82 100644 --- a/fs/sysv/namei.c +++ b/fs/sysv/namei.c @@ -153,19 +153,18 @@ static int sysv_unlink(struct inode * dir, struct dentry * dentry) struct inode * inode = d_inode(dentry); struct page * page; struct sysv_dir_entry * de; - int err = -ENOENT; + int err; de = sysv_find_entry(dentry, &page); if (!de) - goto out; + return -ENOENT; - err = sysv_delete_entry (de, page); - if (err) - goto out; - - inode->i_ctime = dir->i_ctime; - inode_dec_link_count(inode); -out: + err = sysv_delete_entry(de, page); + if (!err) { + inode->i_ctime = dir->i_ctime; + inode_dec_link_count(inode); + } + dir_put_page(page, de); return err; } @@ -227,7 +226,10 @@ static int sysv_rename(struct mnt_idmap *idmap, struct inode *old_dir, new_de = sysv_find_entry(new_dentry, &new_page); if (!new_de) goto out_dir; - sysv_set_link(new_de, new_page, old_inode); + err = sysv_set_link(new_de, new_page, old_inode); + dir_put_page(new_page, new_de); + if (err) + goto out_dir; new_inode->i_ctime = current_time(new_inode); if (dir_de) drop_nlink(new_inode); @@ -240,23 +242,23 @@ static int sysv_rename(struct mnt_idmap *idmap, struct inode *old_dir, inode_inc_link_count(new_dir); } - sysv_delete_entry(old_de, old_page); + err = sysv_delete_entry(old_de, old_page); + if (err) + goto out_dir; + mark_inode_dirty(old_inode); if (dir_de) { - sysv_set_link(dir_de, dir_page, new_dir); - inode_dec_link_count(old_dir); + err = sysv_set_link(dir_de, dir_page, new_dir); + if (!err) + inode_dec_link_count(old_dir); } - return 0; out_dir: - if (dir_de) { - kunmap(dir_page); - put_page(dir_page); - } + if (dir_de) + dir_put_page(dir_page, dir_de); out_old: - kunmap(old_page); - put_page(old_page); + dir_put_page(old_page, old_de); out: return err; } diff --git a/fs/sysv/sysv.h b/fs/sysv/sysv.h index 5e122a5673c1..f2c36ea42df6 100644 --- a/fs/sysv/sysv.h +++ b/fs/sysv/sysv.h @@ -148,12 +148,13 @@ extern void sysv_destroy_icache(void); /* dir.c */ +extern void dir_put_page(struct page *page, void *vaddr); extern struct sysv_dir_entry *sysv_find_entry(struct dentry *, struct page **); extern int sysv_add_link(struct dentry *, struct inode *); extern int sysv_delete_entry(struct sysv_dir_entry *, struct page *); extern int sysv_make_empty(struct inode *, struct inode *); extern int sysv_empty_dir(struct inode *); -extern void sysv_set_link(struct sysv_dir_entry *, struct page *, +extern int sysv_set_link(struct sysv_dir_entry *, struct page *, struct inode *); extern struct sysv_dir_entry *sysv_dotdot(struct inode *, struct page **); extern ino_t sysv_inode_by_name(struct dentry *); |