aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ramfs/file-nommu.c
diff options
context:
space:
mode:
authorDavid Howells <dhowells@redhat.com>2006-01-06 03:11:41 -0500
committerLinus Torvalds <torvalds@g5.osdl.org>2006-01-06 11:33:32 -0500
commit642fb4d1f1dd2417aa69189fe5ceb81e4fb72900 (patch)
tree77427e3141a6160bfa09ed3af4428c6e874f3e0c /fs/ramfs/file-nommu.c
parent9f6d4b0c21a6894dad7665d3dda4174c7c120784 (diff)
[PATCH] NOMMU: Provide shared-writable mmap support on ramfs
The attached patch makes ramfs support shared-writable mmaps by: (1) Attempting to perform a contiguous block allocation to the requested size when truncate attempts to increase the file from zero size, such as happens when: fd = shm_open("/file/on/ramfs", ...): ftruncate(fd, size_requested); addr = mmap(NULL, subsize, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, offset); (2) Permitting any shared-writable mapping over any contiguous set of extant pages. get_unmapped_area() will return the address into the actual ramfs pages. The mapping may start anywhere and be of any size, but may not go over the end of file. Multiple mappings may overlap in any way. (3) Not permitting a file to be shrunk if it would truncate any shared mappings (private mappings are copied). Thus this patch provides support for POSIX shared memory on NOMMU kernels, with certain limitations such as there being a large enough block of pages available to support the allocation and it only working on directly mappable filesystems. Signed-off-by: David Howells <dhowells@redhat.com> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs/ramfs/file-nommu.c')
-rw-r--r--fs/ramfs/file-nommu.c292
1 files changed, 292 insertions, 0 deletions
diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c
new file mode 100644
index 000000000000..3f810acd0bfa
--- /dev/null
+++ b/fs/ramfs/file-nommu.c
@@ -0,0 +1,292 @@
1/* file-nommu.c: no-MMU version of ramfs
2 *
3 * Copyright (C) 2005 Red Hat, Inc. All Rights Reserved.
4 * Written by David Howells (dhowells@redhat.com)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 */
11
12#include <linux/module.h>
13#include <linux/fs.h>
14#include <linux/pagemap.h>
15#include <linux/highmem.h>
16#include <linux/init.h>
17#include <linux/string.h>
18#include <linux/smp_lock.h>
19#include <linux/backing-dev.h>
20#include <linux/ramfs.h>
21#include <linux/quotaops.h>
22#include <linux/pagevec.h>
23#include <linux/mman.h>
24
25#include <asm/uaccess.h>
26#include "internal.h"
27
28static int ramfs_nommu_setattr(struct dentry *, struct iattr *);
29
30struct address_space_operations ramfs_aops = {
31 .readpage = simple_readpage,
32 .prepare_write = simple_prepare_write,
33 .commit_write = simple_commit_write
34};
35
36struct file_operations ramfs_file_operations = {
37 .mmap = ramfs_nommu_mmap,
38 .get_unmapped_area = ramfs_nommu_get_unmapped_area,
39 .read = generic_file_read,
40 .write = generic_file_write,
41 .fsync = simple_sync_file,
42 .sendfile = generic_file_sendfile,
43 .llseek = generic_file_llseek,
44};
45
46struct inode_operations ramfs_file_inode_operations = {
47 .setattr = ramfs_nommu_setattr,
48 .getattr = simple_getattr,
49};
50
51/*****************************************************************************/
52/*
53 * add a contiguous set of pages into a ramfs inode when it's truncated from
54 * size 0 on the assumption that it's going to be used for an mmap of shared
55 * memory
56 */
57static int ramfs_nommu_expand_for_mapping(struct inode *inode, size_t newsize)
58{
59 struct pagevec lru_pvec;
60 unsigned long npages, xpages, loop, limit;
61 struct page *pages;
62 unsigned order;
63 void *data;
64 int ret;
65
66 /* make various checks */
67 order = get_order(newsize);
68 if (unlikely(order >= MAX_ORDER))
69 goto too_big;
70
71 limit = current->signal->rlim[RLIMIT_FSIZE].rlim_cur;
72 if (limit != RLIM_INFINITY && newsize > limit)
73 goto fsize_exceeded;
74
75 if (newsize > inode->i_sb->s_maxbytes)
76 goto too_big;
77
78 i_size_write(inode, newsize);
79
80 /* allocate enough contiguous pages to be able to satisfy the
81 * request */
82 pages = alloc_pages(mapping_gfp_mask(inode->i_mapping), order);
83 if (!pages)
84 return -ENOMEM;
85
86 /* split the high-order page into an array of single pages */
87 xpages = 1UL << order;
88 npages = (newsize + PAGE_SIZE - 1) >> PAGE_SHIFT;
89
90 for (loop = 0; loop < npages; loop++)
91 set_page_count(pages + loop, 1);
92
93 /* trim off any pages we don't actually require */
94 for (loop = npages; loop < xpages; loop++)
95 __free_page(pages + loop);
96
97 /* clear the memory we allocated */
98 newsize = PAGE_SIZE * npages;
99 data = page_address(pages);
100 memset(data, 0, newsize);
101
102 /* attach all the pages to the inode's address space */
103 pagevec_init(&lru_pvec, 0);
104 for (loop = 0; loop < npages; loop++) {
105 struct page *page = pages + loop;
106
107 ret = add_to_page_cache(page, inode->i_mapping, loop, GFP_KERNEL);
108 if (ret < 0)
109 goto add_error;
110
111 if (!pagevec_add(&lru_pvec, page))
112 __pagevec_lru_add(&lru_pvec);
113
114 unlock_page(page);
115 }
116
117 pagevec_lru_add(&lru_pvec);
118 return 0;
119
120 fsize_exceeded:
121 send_sig(SIGXFSZ, current, 0);
122 too_big:
123 return -EFBIG;
124
125 add_error:
126 page_cache_release(pages + loop);
127 for (loop++; loop < npages; loop++)
128 __free_page(pages + loop);
129 return ret;
130}
131
132/*****************************************************************************/
133/*
134 * check that file shrinkage doesn't leave any VMAs dangling in midair
135 */
136static int ramfs_nommu_check_mappings(struct inode *inode,
137 size_t newsize, size_t size)
138{
139 struct vm_area_struct *vma;
140 struct prio_tree_iter iter;
141
142 /* search for VMAs that fall within the dead zone */
143 vma_prio_tree_foreach(vma, &iter, &inode->i_mapping->i_mmap,
144 newsize >> PAGE_SHIFT,
145 (size + PAGE_SIZE - 1) >> PAGE_SHIFT
146 ) {
147 /* found one - only interested if it's shared out of the page
148 * cache */
149 if (vma->vm_flags & VM_SHARED)
150 return -ETXTBSY; /* not quite true, but near enough */
151 }
152
153 return 0;
154}
155
156/*****************************************************************************/
157/*
158 *
159 */
160static int ramfs_nommu_resize(struct inode *inode, loff_t newsize, loff_t size)
161{
162 int ret;
163
164 /* assume a truncate from zero size is going to be for the purposes of
165 * shared mmap */
166 if (size == 0) {
167 if (unlikely(newsize >> 32))
168 return -EFBIG;
169
170 return ramfs_nommu_expand_for_mapping(inode, newsize);
171 }
172
173 /* check that a decrease in size doesn't cut off any shared mappings */
174 if (newsize < size) {
175 ret = ramfs_nommu_check_mappings(inode, newsize, size);
176 if (ret < 0)
177 return ret;
178 }
179
180 ret = vmtruncate(inode, size);
181
182 return ret;
183}
184
185/*****************************************************************************/
186/*
187 * handle a change of attributes
188 * - we're specifically interested in a change of size
189 */
190static int ramfs_nommu_setattr(struct dentry *dentry, struct iattr *ia)
191{
192 struct inode *inode = dentry->d_inode;
193 unsigned int old_ia_valid = ia->ia_valid;
194 int ret = 0;
195
196 /* by providing our own setattr() method, we skip this quotaism */
197 if ((old_ia_valid & ATTR_UID && ia->ia_uid != inode->i_uid) ||
198 (old_ia_valid & ATTR_GID && ia->ia_gid != inode->i_gid))
199 ret = DQUOT_TRANSFER(inode, ia) ? -EDQUOT : 0;
200
201 /* pick out size-changing events */
202 if (ia->ia_valid & ATTR_SIZE) {
203 loff_t size = i_size_read(inode);
204 if (ia->ia_size != size) {
205 ret = ramfs_nommu_resize(inode, ia->ia_size, size);
206 if (ret < 0 || ia->ia_valid == ATTR_SIZE)
207 goto out;
208 } else {
209 /* we skipped the truncate but must still update
210 * timestamps
211 */
212 ia->ia_valid |= ATTR_MTIME|ATTR_CTIME;
213 }
214 }
215
216 ret = inode_setattr(inode, ia);
217 out:
218 ia->ia_valid = old_ia_valid;
219 return ret;
220}
221
222/*****************************************************************************/
223/*
224 * try to determine where a shared mapping can be made
225 * - we require that:
226 * - the pages to be mapped must exist
227 * - the pages be physically contiguous in sequence
228 */
229unsigned long ramfs_nommu_get_unmapped_area(struct file *file,
230 unsigned long addr, unsigned long len,
231 unsigned long pgoff, unsigned long flags)
232{
233 unsigned long maxpages, lpages, nr, loop, ret;
234 struct inode *inode = file->f_dentry->d_inode;
235 struct page **pages = NULL, **ptr, *page;
236 loff_t isize;
237
238 if (!(flags & MAP_SHARED))
239 return addr;
240
241 /* the mapping mustn't extend beyond the EOF */
242 lpages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
243 isize = i_size_read(inode);
244
245 ret = -EINVAL;
246 maxpages = (isize + PAGE_SIZE - 1) >> PAGE_SHIFT;
247 if (pgoff >= maxpages)
248 goto out;
249
250 if (maxpages - pgoff < lpages)
251 goto out;
252
253 /* gang-find the pages */
254 ret = -ENOMEM;
255 pages = kzalloc(lpages * sizeof(struct page *), GFP_KERNEL);
256 if (!pages)
257 goto out;
258
259 nr = find_get_pages(inode->i_mapping, pgoff, lpages, pages);
260 if (nr != lpages)
261 goto out; /* leave if some pages were missing */
262
263 /* check the pages for physical adjacency */
264 ptr = pages;
265 page = *ptr++;
266 page++;
267 for (loop = lpages; loop > 1; loop--)
268 if (*ptr++ != page++)
269 goto out;
270
271 /* okay - all conditions fulfilled */
272 ret = (unsigned long) page_address(pages[0]);
273
274 out:
275 if (pages) {
276 ptr = pages;
277 for (loop = lpages; loop > 0; loop--)
278 put_page(*ptr++);
279 kfree(pages);
280 }
281
282 return ret;
283}
284
285/*****************************************************************************/
286/*
287 * set up a mapping
288 */
289int ramfs_nommu_mmap(struct file *file, struct vm_area_struct *vma)
290{
291 return 0;
292}