Commit e666acdf authored by Andrew Morton's avatar Andrew Morton Committed by Christoph Hellwig

[PATCH] tmpfs: shmem_file_write update

Patch from Hugh Dickins

Checked shmem_file_write against recent filemap source, and against
2.4 and 2.4-ac: folded in missing fixes, mostly related to far file
positions.  Plus the new kmap_atomic copying technique.  But for now,
as before, no mark_page_accessed or SetPageReferenced in shmem.c: add
those, or whatever, later on when akpm has reviewed usage elsewhere.
parent 2f28cc35
...@@ -1062,12 +1062,46 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos) ...@@ -1062,12 +1062,46 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
send_sig(SIGXFSZ, current, 0); send_sig(SIGXFSZ, current, 0);
goto out; goto out;
} }
if (count > limit - pos) { if (pos > 0xFFFFFFFFULL || count > limit - (u32)pos) {
/* send_sig(SIGXFSZ, current, 0); */
count = limit - (u32)pos;
}
}
/*
* LFS rule
*/
if (pos + count > MAX_NON_LFS && !(file->f_flags&O_LARGEFILE)) {
if (pos >= MAX_NON_LFS) {
send_sig(SIGXFSZ, current, 0); send_sig(SIGXFSZ, current, 0);
count = limit - pos; goto out;
}
if (count > MAX_NON_LFS - (u32)pos) {
/* send_sig(SIGXFSZ, current, 0); */
count = MAX_NON_LFS - (u32)pos;
} }
} }
/*
* Are we about to exceed the fs block limit ?
*
* If we have written data it becomes a short write
* If we have exceeded without writing data we send
* a signal and give them an EFBIG.
*
* Linus frestrict idea will clean these up nicely..
*/
if (pos >= SHMEM_MAX_BYTES) {
if (count || pos > SHMEM_MAX_BYTES) {
send_sig(SIGXFSZ, current, 0);
err = -EFBIG;
goto out;
}
/* zero-length writes at ->s_maxbytes are OK */
}
if (pos + count > SHMEM_MAX_BYTES)
count = SHMEM_MAX_BYTES - pos;
status = 0; status = 0;
if (count) { if (count) {
remove_suid(file->f_dentry); remove_suid(file->f_dentry);
...@@ -1077,36 +1111,53 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos) ...@@ -1077,36 +1111,53 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
while (count) { while (count) {
unsigned long bytes, index, offset; unsigned long bytes, index, offset;
char *kaddr; char *kaddr;
int left;
/*
* Try to find the page in the cache. If it isn't there,
* allocate a free page.
*/
offset = (pos & (PAGE_CACHE_SIZE -1)); /* Within page */ offset = (pos & (PAGE_CACHE_SIZE -1)); /* Within page */
index = pos >> PAGE_CACHE_SHIFT; index = pos >> PAGE_CACHE_SHIFT;
bytes = PAGE_CACHE_SIZE - offset; bytes = PAGE_CACHE_SIZE - offset;
if (bytes > count) { if (bytes > count)
bytes = count; bytes = count;
}
/* /*
* We don't hold page lock across copy from user - * We don't hold page lock across copy from user -
* what would it guard against? - so no deadlock here. * what would it guard against? - so no deadlock here.
* But it still may be a good idea to prefault below.
*/ */
status = shmem_getpage(inode, index, &page, SGP_WRITE); status = shmem_getpage(inode, index, &page, SGP_WRITE);
if (status) if (status)
break; break;
left = bytes;
if (PageHighMem(page)) {
volatile unsigned char dummy;
__get_user(dummy, buf);
__get_user(dummy, buf + bytes - 1);
kaddr = kmap_atomic(page, KM_USER0);
left = __copy_from_user(kaddr + offset, buf, bytes);
kunmap_atomic(kaddr, KM_USER0);
}
if (left) {
kaddr = kmap(page); kaddr = kmap(page);
status = __copy_from_user(kaddr+offset, buf, bytes); left = __copy_from_user(kaddr + offset, buf, bytes);
kunmap(page); kunmap(page);
if (status) }
goto fail_write;
flush_dcache_page(page); flush_dcache_page(page);
if (bytes > 0) { if (left) {
page_cache_release(page);
status = -EFAULT;
break;
}
set_page_dirty(page); set_page_dirty(page);
page_cache_release(page);
/*
* Balance dirty pages??
*/
written += bytes; written += bytes;
count -= bytes; count -= bytes;
pos += bytes; pos += bytes;
...@@ -1114,14 +1165,8 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos) ...@@ -1114,14 +1165,8 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
if (pos > inode->i_size) if (pos > inode->i_size)
inode->i_size = pos; inode->i_size = pos;
} }
release:
page_cache_release(page);
if (status < 0)
break;
}
*ppos = pos; *ppos = pos;
err = written ? written : status; err = written ? written : status;
out: out:
/* Short writes give back address space */ /* Short writes give back address space */
...@@ -1130,10 +1175,6 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos) ...@@ -1130,10 +1175,6 @@ shmem_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
out_nc: out_nc:
up(&inode->i_sem); up(&inode->i_sem);
return err; return err;
fail_write:
status = -EFAULT;
ClearPageUptodate(page);
goto release;
} }
static void do_shmem_file_read(struct file *filp, loff_t *ppos, read_descriptor_t *desc, read_actor_t actor) static void do_shmem_file_read(struct file *filp, loff_t *ppos, read_descriptor_t *desc, read_actor_t actor)
...@@ -1407,9 +1448,9 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s ...@@ -1407,9 +1448,9 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
spin_lock(&shmem_ilock); spin_lock(&shmem_ilock);
list_add_tail(&info->list, &shmem_inodes); list_add_tail(&info->list, &shmem_inodes);
spin_unlock(&shmem_ilock); spin_unlock(&shmem_ilock);
kaddr = kmap(page); kaddr = kmap_atomic(page, KM_USER0);
memcpy(kaddr, symname, len); memcpy(kaddr, symname, len);
kunmap(page); kunmap_atomic(kaddr, KM_USER0);
set_page_dirty(page); set_page_dirty(page);
page_cache_release(page); page_cache_release(page);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment