diff -urN linux-2.2.10/fs/ext2/file.c linux-2.2.10.SuSE/fs/ext2/file.c --- linux-2.2.10/fs/ext2/file.c Tue Jun 1 21:43:35 1999 +++ linux-2.2.10.SuSE/fs/ext2/file.c Fri Jul 16 02:39:13 1999 @@ -295,7 +295,8 @@ break; } mark_buffer_dirty(bh, 0); - update_vm_cache(inode, pos, bh->b_data + offset, c); + update_vm_cache_conditional(inode, pos, bh->b_data + offset, c, + (unsigned long) buf); pos += c; written += c; buf += c; diff -urN linux-2.2.10/include/linux/pagemap.h linux-2.2.10.SuSE/include/linux/pagemap.h --- linux-2.2.10/include/linux/pagemap.h Tue May 11 19:35:49 1999 +++ linux-2.2.10.SuSE/include/linux/pagemap.h Fri Jul 16 02:39:13 1999 @@ -148,6 +148,7 @@ __wait_on_page(page); } +extern void update_vm_cache_conditional(struct inode *, unsigned long, const char *, int, unsigned long); extern void update_vm_cache(struct inode *, unsigned long, const char *, int); #endif diff -urN linux-2.2.10/kernel/ksyms.c linux-2.2.10.SuSE/kernel/ksyms.c --- linux-2.2.10/kernel/ksyms.c Fri Apr 30 00:06:32 1999 +++ linux-2.2.10.SuSE/kernel/ksyms.c Fri Jul 16 02:39:13 1999 @@ -105,6 +105,7 @@ EXPORT_SYMBOL(max_mapnr); EXPORT_SYMBOL(high_memory); EXPORT_SYMBOL(update_vm_cache); +EXPORT_SYMBOL(update_vm_cache_conditional); EXPORT_SYMBOL(vmtruncate); EXPORT_SYMBOL(find_vma); EXPORT_SYMBOL(get_unmapped_area); diff -urN linux-2.2.10/mm/filemap.c linux-2.2.10.SuSE/mm/filemap.c --- linux-2.2.10/mm/filemap.c Tue May 11 17:51:13 1999 +++ linux-2.2.10.SuSE/mm/filemap.c Fri Jul 16 02:39:13 1999 @@ -215,8 +215,25 @@ /* * Update a page cache copy, when we're doing a "write()" system call * See also "update_vm_cache()". + * + * This function is conditional in that it checks whether the original + * source of the data is the same as the ultimate destination, and + * aborts the update if so. + * + * The "source_address" is the virtual address of the original location + * of the data we are injecting. For writes from user mode, it is the + * user VA. However, for filemap_sync writes, "source_address", it is + * the page cache address. In both cases, "buf" points to the copy we + * have already made in kernel space and we use that pointer for the + * transfer. source_address just allows us to detect an update_vm_cache + * which is being sourced from the copy of the data already in the page + * cache. + * + * This prevents munmap() and msync() from stomping all over shared + * memory maps. --sct */ -void update_vm_cache(struct inode * inode, unsigned long pos, const char * buf, int count) + +void update_vm_cache_conditional(struct inode * inode, unsigned long pos, const char * buf, int count, unsigned long source_address) { unsigned long offset, len; @@ -230,8 +247,12 @@ len = count; page = find_page(inode, pos); if (page) { - wait_on_page(page); - memcpy((void *) (offset + page_address(page)), buf, len); + char *dest = (char*) (offset + page_address(page)); + + if (dest != source_address) { + wait_on_page(page); + memcpy(dest, buf, len); + } page_cache_release(page); } count -= len; @@ -242,6 +263,12 @@ } while (count); } +void update_vm_cache(struct inode * inode, unsigned long pos, const char * buf, int count) +{ + update_vm_cache_conditional(inode, pos, buf, count, 0); +} + + static inline void add_to_page_cache(struct page * page, struct inode * inode, unsigned long offset, struct page **hash) @@ -1482,6 +1509,8 @@ while (count) { unsigned long bytes, pgpos, offset; + char * dest; + /* * Try to find the page in the cache. If it isn't there, * allocate a free page. @@ -1516,7 +1545,9 @@ * the writer needs to increment the page use counts until he * is done with the page. */ - bytes -= copy_from_user((u8*)page_address(page) + offset, buf, bytes); + dest = (char *) page_address(page) + offset; + if (dest != buf) /* See comment in update_vm_cache_cond. */ + bytes -= copy_from_user(dest, buf, bytes); status = -EFAULT; if (bytes) status = inode->i_op->updatepage(file, page, offset, bytes, sync);