diff options
author | OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> | 2006-01-08 04:02:13 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-01-08 23:13:47 -0500 |
commit | 05eb0b51fb46430050d5873458612f53e0234f2e (patch) | |
tree | 88b9398079f53f29dff7533fb27487ae725983b1 | |
parent | 268fc16e343b4f8e249468747db2e658da46a814 (diff) |
[PATCH] fat: support a truncate() for expanding size (generic_cont_expand)
This patch changes generic_cont_expand(), in order to share the code
with fatfs.
- Use vmtruncate() if ->prepare_write() returns a error.
Even if ->prepare_write() returns an error, it may already have added some
blocks. So, this truncates blocks outside of ->i_size by vmtruncate().
- Add generic_cont_expand_simple().
The generic_cont_expand_simple() assumes that ->prepare_write() can handle
the block boundary. With this, we don't need to care the extra byte.
And for expanding a file size by truncate(), fatfs uses the
added generic_cont_expand_simple().
Signed-off-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r-- | fs/buffer.c | 60 | ||||
-rw-r--r-- | fs/fat/file.c | 31 | ||||
-rw-r--r-- | include/linux/buffer_head.h | 3 |
3 files changed, 76 insertions, 18 deletions
diff --git a/fs/buffer.c b/fs/buffer.c index 5287be18633b..55023231e460 100644 --- a/fs/buffer.c +++ b/fs/buffer.c | |||
@@ -2160,11 +2160,12 @@ int block_read_full_page(struct page *page, get_block_t *get_block) | |||
2160 | * truncates. Uses prepare/commit_write to allow the filesystem to | 2160 | * truncates. Uses prepare/commit_write to allow the filesystem to |
2161 | * deal with the hole. | 2161 | * deal with the hole. |
2162 | */ | 2162 | */ |
2163 | int generic_cont_expand(struct inode *inode, loff_t size) | 2163 | static int __generic_cont_expand(struct inode *inode, loff_t size, |
2164 | pgoff_t index, unsigned int offset) | ||
2164 | { | 2165 | { |
2165 | struct address_space *mapping = inode->i_mapping; | 2166 | struct address_space *mapping = inode->i_mapping; |
2166 | struct page *page; | 2167 | struct page *page; |
2167 | unsigned long index, offset, limit; | 2168 | unsigned long limit; |
2168 | int err; | 2169 | int err; |
2169 | 2170 | ||
2170 | err = -EFBIG; | 2171 | err = -EFBIG; |
@@ -2176,24 +2177,24 @@ int generic_cont_expand(struct inode *inode, loff_t size) | |||
2176 | if (size > inode->i_sb->s_maxbytes) | 2177 | if (size > inode->i_sb->s_maxbytes) |
2177 | goto out; | 2178 | goto out; |
2178 | 2179 | ||
2179 | offset = (size & (PAGE_CACHE_SIZE-1)); /* Within page */ | ||
2180 | |||
2181 | /* ugh. in prepare/commit_write, if from==to==start of block, we | ||
2182 | ** skip the prepare. make sure we never send an offset for the start | ||
2183 | ** of a block | ||
2184 | */ | ||
2185 | if ((offset & (inode->i_sb->s_blocksize - 1)) == 0) { | ||
2186 | offset++; | ||
2187 | } | ||
2188 | index = size >> PAGE_CACHE_SHIFT; | ||
2189 | err = -ENOMEM; | 2180 | err = -ENOMEM; |
2190 | page = grab_cache_page(mapping, index); | 2181 | page = grab_cache_page(mapping, index); |
2191 | if (!page) | 2182 | if (!page) |
2192 | goto out; | 2183 | goto out; |
2193 | err = mapping->a_ops->prepare_write(NULL, page, offset, offset); | 2184 | err = mapping->a_ops->prepare_write(NULL, page, offset, offset); |
2194 | if (!err) { | 2185 | if (err) { |
2195 | err = mapping->a_ops->commit_write(NULL, page, offset, offset); | 2186 | /* |
2187 | * ->prepare_write() may have instantiated a few blocks | ||
2188 | * outside i_size. Trim these off again. | ||
2189 | */ | ||
2190 | unlock_page(page); | ||
2191 | page_cache_release(page); | ||
2192 | vmtruncate(inode, inode->i_size); | ||
2193 | goto out; | ||
2196 | } | 2194 | } |
2195 | |||
2196 | err = mapping->a_ops->commit_write(NULL, page, offset, offset); | ||
2197 | |||
2197 | unlock_page(page); | 2198 | unlock_page(page); |
2198 | page_cache_release(page); | 2199 | page_cache_release(page); |
2199 | if (err > 0) | 2200 | if (err > 0) |
@@ -2202,6 +2203,36 @@ out: | |||
2202 | return err; | 2203 | return err; |
2203 | } | 2204 | } |
2204 | 2205 | ||
2206 | int generic_cont_expand(struct inode *inode, loff_t size) | ||
2207 | { | ||
2208 | pgoff_t index; | ||
2209 | unsigned int offset; | ||
2210 | |||
2211 | offset = (size & (PAGE_CACHE_SIZE - 1)); /* Within page */ | ||
2212 | |||
2213 | /* ugh. in prepare/commit_write, if from==to==start of block, we | ||
2214 | ** skip the prepare. make sure we never send an offset for the start | ||
2215 | ** of a block | ||
2216 | */ | ||
2217 | if ((offset & (inode->i_sb->s_blocksize - 1)) == 0) { | ||
2218 | /* caller must handle this extra byte. */ | ||
2219 | offset++; | ||
2220 | } | ||
2221 | index = size >> PAGE_CACHE_SHIFT; | ||
2222 | |||
2223 | return __generic_cont_expand(inode, size, index, offset); | ||
2224 | } | ||
2225 | |||
2226 | int generic_cont_expand_simple(struct inode *inode, loff_t size) | ||
2227 | { | ||
2228 | loff_t pos = size - 1; | ||
2229 | pgoff_t index = pos >> PAGE_CACHE_SHIFT; | ||
2230 | unsigned int offset = (pos & (PAGE_CACHE_SIZE - 1)) + 1; | ||
2231 | |||
2232 | /* prepare/commit_write can handle even if from==to==start of block. */ | ||
2233 | return __generic_cont_expand(inode, size, index, offset); | ||
2234 | } | ||
2235 | |||
2205 | /* | 2236 | /* |
2206 | * For moronic filesystems that do not allow holes in file. | 2237 | * For moronic filesystems that do not allow holes in file. |
2207 | * We may have to extend the file. | 2238 | * We may have to extend the file. |
@@ -3145,6 +3176,7 @@ EXPORT_SYMBOL(fsync_bdev); | |||
3145 | EXPORT_SYMBOL(generic_block_bmap); | 3176 | EXPORT_SYMBOL(generic_block_bmap); |
3146 | EXPORT_SYMBOL(generic_commit_write); | 3177 | EXPORT_SYMBOL(generic_commit_write); |
3147 | EXPORT_SYMBOL(generic_cont_expand); | 3178 | EXPORT_SYMBOL(generic_cont_expand); |
3179 | EXPORT_SYMBOL(generic_cont_expand_simple); | ||
3148 | EXPORT_SYMBOL(init_buffer); | 3180 | EXPORT_SYMBOL(init_buffer); |
3149 | EXPORT_SYMBOL(invalidate_bdev); | 3181 | EXPORT_SYMBOL(invalidate_bdev); |
3150 | EXPORT_SYMBOL(ll_rw_block); | 3182 | EXPORT_SYMBOL(ll_rw_block); |
diff --git a/fs/fat/file.c b/fs/fat/file.c index 15229fe569c3..9b07c328a6fc 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c | |||
@@ -11,6 +11,7 @@ | |||
11 | #include <linux/msdos_fs.h> | 11 | #include <linux/msdos_fs.h> |
12 | #include <linux/smp_lock.h> | 12 | #include <linux/smp_lock.h> |
13 | #include <linux/buffer_head.h> | 13 | #include <linux/buffer_head.h> |
14 | #include <linux/writeback.h> | ||
14 | 15 | ||
15 | int fat_generic_ioctl(struct inode *inode, struct file *filp, | 16 | int fat_generic_ioctl(struct inode *inode, struct file *filp, |
16 | unsigned int cmd, unsigned long arg) | 17 | unsigned int cmd, unsigned long arg) |
@@ -124,6 +125,24 @@ struct file_operations fat_file_operations = { | |||
124 | .sendfile = generic_file_sendfile, | 125 | .sendfile = generic_file_sendfile, |
125 | }; | 126 | }; |
126 | 127 | ||
128 | static int fat_cont_expand(struct inode *inode, loff_t size) | ||
129 | { | ||
130 | struct address_space *mapping = inode->i_mapping; | ||
131 | loff_t start = inode->i_size, count = size - inode->i_size; | ||
132 | int err; | ||
133 | |||
134 | err = generic_cont_expand_simple(inode, size); | ||
135 | if (err) | ||
136 | goto out; | ||
137 | |||
138 | inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; | ||
139 | mark_inode_dirty(inode); | ||
140 | if (IS_SYNC(inode)) | ||
141 | err = sync_page_range_nolock(inode, mapping, start, count); | ||
142 | out: | ||
143 | return err; | ||
144 | } | ||
145 | |||
127 | int fat_notify_change(struct dentry *dentry, struct iattr *attr) | 146 | int fat_notify_change(struct dentry *dentry, struct iattr *attr) |
128 | { | 147 | { |
129 | struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb); | 148 | struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb); |
@@ -132,11 +151,17 @@ int fat_notify_change(struct dentry *dentry, struct iattr *attr) | |||
132 | 151 | ||
133 | lock_kernel(); | 152 | lock_kernel(); |
134 | 153 | ||
135 | /* FAT cannot truncate to a longer file */ | 154 | /* |
155 | * Expand the file. Since inode_setattr() updates ->i_size | ||
156 | * before calling the ->truncate(), but FAT needs to fill the | ||
157 | * hole before it. | ||
158 | */ | ||
136 | if (attr->ia_valid & ATTR_SIZE) { | 159 | if (attr->ia_valid & ATTR_SIZE) { |
137 | if (attr->ia_size > inode->i_size) { | 160 | if (attr->ia_size > inode->i_size) { |
138 | error = -EPERM; | 161 | error = fat_cont_expand(inode, attr->ia_size); |
139 | goto out; | 162 | if (error || attr->ia_valid == ATTR_SIZE) |
163 | goto out; | ||
164 | attr->ia_valid &= ~ATTR_SIZE; | ||
140 | } | 165 | } |
141 | } | 166 | } |
142 | 167 | ||
diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 1db061bb6b08..9f159baf153f 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h | |||
@@ -197,7 +197,8 @@ int block_read_full_page(struct page*, get_block_t*); | |||
197 | int block_prepare_write(struct page*, unsigned, unsigned, get_block_t*); | 197 | int block_prepare_write(struct page*, unsigned, unsigned, get_block_t*); |
198 | int cont_prepare_write(struct page*, unsigned, unsigned, get_block_t*, | 198 | int cont_prepare_write(struct page*, unsigned, unsigned, get_block_t*, |
199 | loff_t *); | 199 | loff_t *); |
200 | int generic_cont_expand(struct inode *inode, loff_t size) ; | 200 | int generic_cont_expand(struct inode *inode, loff_t size); |
201 | int generic_cont_expand_simple(struct inode *inode, loff_t size); | ||
201 | int block_commit_write(struct page *page, unsigned from, unsigned to); | 202 | int block_commit_write(struct page *page, unsigned from, unsigned to); |
202 | int block_sync_page(struct page *); | 203 | int block_sync_page(struct page *); |
203 | sector_t generic_block_bmap(struct address_space *, sector_t, get_block_t *); | 204 | sector_t generic_block_bmap(struct address_space *, sector_t, get_block_t *); |