// SPDX-License-Identifier: GPL-2.0-or-later/* handling of writes to regular files and writing back to the server * * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) */ #include <linux/backing-dev.h>#include <linux/slab.h>#include <linux/fs.h>#include <linux/pagemap.h>#include <linux/writeback.h>#include <linux/pagevec.h>#include "internal.h" /* * mark a page as having been made dirty and thus needing writeback */int afs_set_page_dirty(struct page *page){ _enter(""); return __set_page_dirty_nobuffers(page);} /* * partly or wholly fill a page that's under preparation for writing */static int afs_fill_page(struct afs_vnode *vnode, struct key *key, loff_t pos, unsigned int len, struct page *page){ struct afs_read *req; size_t p; void *data; int ret; _enter(",,%llu", (unsigned long long)pos); if (pos >= vnode->vfs_inode.i_size) { p = pos & ~PAGE_MASK; ASSERTCMP(p + len, <=, PAGE_SIZE); data = kmap(page); memset(data + p, 0, len); kunmap(page); return 0; } req = kzalloc(struct_size(req, array, 1), GFP_KERNEL); if (!req) return -ENOMEM; refcount_set(&req->usage, 1); req->pos = pos; req->len = len; req->nr_pages = 1; req->pages = req->array; req->pages[0] = page; get_page(page); ret = afs_fetch_data(vnode, key, req); afs_put_read(req); if (ret < 0) { if (ret == -ENOENT) { _debug("got NOENT from server" " - marking file deleted and stale"); set_bit(AFS_VNODE_DELETED, &vnode->flags); ret = -ESTALE; } } _leave(" = %d", ret); return ret;} /* * prepare to perform part of a write to a page */int afs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep, void **fsdata){ struct afs_vnode *vnode = AFS_FS_I(file_inode(file)); struct page *page; struct key *key = afs_file_key(file); unsigned long priv; unsigned f, from = pos & (PAGE_SIZE - 1); unsigned t, to = from + len; pgoff_t index = pos >> PAGE_SHIFT; int ret; _enter("{%llx:%llu},{%lx},%u,%u", vnode->fid.vid, vnode->fid.vnode, index, from, to); /* We want to store information about how much of a page is altered in * page->private. */ BUILD_BUG_ON(PAGE_SIZE > 32768 && sizeof(page->private) < 8); page = grab_cache_page_write_begin(mapping, index, flags); if (!page) return -ENOMEM; if (!PageUptodate(page) && len != PAGE_SIZE) { ret = afs_fill_page(vnode, key, pos & PAGE_MASK, PAGE_SIZE, page); if (ret < 0) { unlock_page(page); put_page(page); _leave(" = %d [prep]", ret); return ret; } SetPageUptodate(page); } /* page won't leak in error case: it eventually gets cleaned off LRU */ *pagep = page; try_again: /* See if this page is already partially written in a way that we can * merge the new write with. */ t = f = 0; if (PagePrivate(page)) { priv = page_private(page); f = priv & AFS_PRIV_MAX; t = priv >> AFS_PRIV_SHIFT; ASSERTCMP(f, <=, t); } if (f != t) { if (PageWriteback(page)) { trace_afs_page_dirty(vnode, tracepoint_string("alrdy"), page->index, priv); goto flush_conflicting_write; } /* If the file is being filled locally, allow inter-write * spaces to be merged into writes. If it's not, only write * back what the user gives us. */ if (!test_bit(AFS_VNODE_NEW_CONTENT, &vnode->flags) && (to < f || from > t)) goto flush_conflicting_write; if (from < f) f = from; if (to > t) t = to; } else { f = from; t = to; } priv = (unsigned long)t << AFS_PRIV_SHIFT; priv |= f; trace_afs_page_dirty(vnode, tracepoint_string("begin"), page->index, priv); SetPagePrivate(page); set_page_private(page, priv); _leave(" = 0"); return 0; /* The previous write and this write aren't adjacent or overlapping, so * flush the page out. */flush_conflicting_write: _debug("flush conflict"); ret = write_one_page(page); if (ret < 0) { _leave(" = %d", ret); return ret; } ret = lock_page_killable(page); if (ret < 0) { _leave(" = %d", ret); return ret; } goto try_again;} /* * finalise part of a write to a page */int afs_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata){ struct afs_vnode *vnode = AFS_FS_I(file_inode(file)); struct key *key = afs_file_key(file); loff_t i_size, maybe_i_size; int ret; _enter("{%llx:%llu},{%lx}", vnode->fid.vid, vnode->fid.vnode, page->index); maybe_i_size = pos + copied; i_size = i_size_read(&vnode->vfs_inode); if (maybe_i_size > i_size) { write_seqlock(&vnode->cb_lock); i_size = i_size_read(&vnode->vfs_inode); if (maybe_i_size > i_size) i_size_write(&vnode->vfs_inode, maybe_i_size); write_sequnlock(&vnode->cb_lock); } if (!PageUptodate(page)) { if (copied < len) { /* Try and load any missing data from the server. The * unmarshalling routine will take care of clearing any * bits that are beyond the EOF. */ ret = afs_fill_page(vnode, key, pos + copied, len - copied, page); if (ret < 0) goto out; } SetPageUptodate(page); } set_page_dirty(page); if (PageDirty(page)) _debug("dirtied"); ret = copied; out: unlock_page(page); put_page(page); return ret;} /* * kill all the pages in the given range */static void afs_kill_pages(struct address_space *mapping, pgoff_t first, pgoff_t last){ struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct pagevec pv; unsigned count, loop; _enter("{%llx:%llu},%lx-%lx", vnode->fid.vid, vnode->fid.vnode, first, last); pagevec_init(&pv); do { _debug("kill %lx-%lx", first, last); count = last - first + 1; if (count > PAGEVEC_SIZE) count = PAGEVEC_SIZE; pv.nr = find_get_pages_contig(mapping, first, count, pv.pages); ASSERTCMP(pv.nr, ==, count); for (loop = 0; loop < count; loop++) { struct page *page = pv.pages[loop]; ClearPageUptodate(page); SetPageError(page); end_page_writeback(page); if (page->index >= first) first = page->index + 1; lock_page(page); generic_error_remove_page(mapping, page); unlock_page(page); } __pagevec_release(&pv); } while (first <= last); _leave("");} /* * Redirty all the pages in a given range. */static void afs_redirty_pages(struct writeback_control *wbc, struct address_space *mapping, pgoff_t first, pgoff_t last){ struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct pagevec pv; unsigned count, loop; _enter("{%llx:%llu},%lx-%lx", vnode->fid.vid, vnode->fid.vnode, first, last); pagevec_init(&pv); do { _debug("redirty %lx-%lx", first, last); count = last - first + 1; if (count > PAGEVEC_SIZE) count = PAGEVEC_SIZE; pv.nr = find_get_pages_contig(mapping, first, count, pv.pages); ASSERTCMP(pv.nr, ==, count); for (loop = 0; loop < count; loop++) { struct page *page = pv.pages[loop]; redirty_page_for_writepage(wbc, page); end_page_writeback(page); if (page->index >= first) first = page->index + 1; } __pagevec_release(&pv); } while (first <= last); _leave("");} /* * completion of write to server */static void afs_pages_written_back(struct afs_vnode *vnode, pgoff_t first, pgoff_t last){ struct pagevec pv; unsigned long priv; unsigned count, loop; _enter("{%llx:%llu},{%lx-%lx}", vnode->fid.vid, vnode->fid.vnode, first, last); pagevec_init(&pv); do { _debug("done %lx-%lx", first, last); count = last - first + 1; if (count > PAGEVEC_SIZE) count = PAGEVEC_SIZE; pv.nr = find_get_pages_contig(vnode->vfs_inode.i_mapping, first, count, pv.pages); ASSERTCMP(pv.nr, ==, count); for (loop = 0; loop < count; loop++) { priv = page_private(pv.pages[loop]); trace_afs_page_dirty(vnode, tracepoint_string("clear"), pv.pages[loop]->index, priv); set_page_private(pv.pages[loop], 0); end_page_writeback(pv.pages[loop]); } first += count; __pagevec_release(&pv); } while (first <= last); afs_prune_wb_keys(vnode); _leave("");} /* * Find a key to use for the writeback. We cached the keys used to author the * writes on the vnode. *_wbk will contain the last writeback key used or NULL * and we need to start from there if it's set. */static int afs_get_writeback_key(struct afs_vnode *vnode, struct afs_wb_key **_wbk){ struct afs_wb_key *wbk = NULL; struct list_head *p; int ret = -ENOKEY, ret2; spin_lock(&vnode->wb_lock); if (*_wbk) p = (*_wbk)->vnode_link.next; else p = vnode->wb_keys.next; while (p != &vnode->wb_keys) { wbk = list_entry(p, struct afs_wb_key, vnode_link); _debug("wbk %u", key_serial(wbk->key)); ret2 = key_validate(wbk->key); if (ret2 == 0) { refcount_inc(&wbk->usage); _debug("USE WB KEY %u", key_serial(wbk->key)); break; } wbk = NULL; if (ret == -ENOKEY) ret = ret2; p = p->next; } spin_unlock(&vnode->wb_lock); if (*_wbk) afs_put_wb_key(*_wbk); *_wbk = wbk; return 0;} static void afs_store_data_success(struct afs_operation *op){ struct afs_vnode *vnode = op->file[0].vnode; op->ctime = op->file[0].scb.status.mtime_client; afs_vnode_commit_status(op, &op->file[0]); if (op->error == 0) { afs_pages_written_back(vnode, op->store.first, op->store.last); afs_stat_v(vnode, n_stores); atomic_long_add((op->store.last * PAGE_SIZE + op->store.last_to) - (op->store.first * PAGE_SIZE + op->store.first_offset), &afs_v2net(vnode)->n_store_bytes); }} static const struct afs_operation_ops afs_store_data_operation = { .issue_afs_rpc = afs_fs_store_data, .issue_yfs_rpc = yfs_fs_store_data, .success = afs_store_data_success,}; /* * write to a file */static int afs_store_data(struct address_space *mapping, pgoff_t first, pgoff_t last, unsigned offset, unsigned to){ struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct afs_operation *op; struct afs_wb_key *wbk = NULL; int ret; _enter("%s{%llx:%llu.%u},%lx,%lx,%x,%x", vnode->volume->name, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique, first, last, offset, to); ret = afs_get_writeback_key(vnode, &wbk); if (ret) { _leave(" = %d [no keys]", ret); return ret; } op = afs_alloc_operation(wbk->key, vnode->volume); if (IS_ERR(op)) { afs_put_wb_key(wbk); return -ENOMEM; } afs_op_set_vnode(op, 0, vnode); op->file[0].dv_delta = 1; op->store.mapping = mapping; op->store.first = first; op->store.last = last; op->store.first_offset = offset; op->store.last_to = to; op->mtime = vnode->vfs_inode.i_mtime; op->flags |= AFS_OPERATION_UNINTR; op->ops = &afs_store_data_operation; try_next_key: afs_begin_vnode_operation(op); afs_wait_for_operation(op); switch (op->error) { case -EACCES: case -EPERM: case -ENOKEY: case -EKEYEXPIRED: case -EKEYREJECTED: case -EKEYREVOKED: _debug("next"); ret = afs_get_writeback_key(vnode, &wbk); if (ret == 0) { key_put(op->key); op->key = key_get(wbk->key); goto try_next_key; } break; } afs_put_wb_key(wbk); _leave(" = %d", op->error); return afs_put_operation(op);} /* * Synchronously write back the locked page and any subsequent non-locked dirty * pages. */static int afs_write_back_from_locked_page(struct address_space *mapping, struct writeback_control *wbc, struct page *primary_page, pgoff_t final_page){ struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct page *pages[8], *page; unsigned long count, priv; unsigned n, offset, to, f, t; pgoff_t start, first, last; loff_t i_size, end; int loop, ret; _enter(",%lx", primary_page->index); Lines 1–500 of 928. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* handling of writes to regular files and writing back to the server * * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) */ #include <linux/backing-dev.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/pagemap.h> #include <linux/writeback.h> #include <linux/pagevec.h> #include "internal.h" /* * mark a page as having been made dirty and thus needing writeback */ int afs_set_page_dirty(struct page *page) { _enter(""); return __set_page_dirty_nobuffers(page); } /* * partly or wholly fill a page that's under preparation for writing */ static int afs_fill_page(struct afs_vnode *vnode, struct key *key, loff_t pos, unsigned int len, struct page *page) { struct afs_read *req; size_t p; void *data; int ret; _enter(",,%llu", (unsigned long long)pos); if (pos >= vnode->vfs_inode.i_size) { p = pos & ~PAGE_MASK; ASSERTCMP(p + len, <=, PAGE_SIZE); data = kmap(page); memset(data + p, 0, len); kunmap(page); return 0; } req = kzalloc(struct_size(req, array, 1), GFP_KERNEL); if (!req) return -ENOMEM; refcount_set(&req->usage, 1); req->pos = pos; req->len = len; req->nr_pages = 1; req->pages = req->array; req->pages[0] = page; get_page(page); ret = afs_fetch_data(vnode, key, req); afs_put_read(req); if (ret < 0) { if (ret == -ENOENT) { _debug("got NOENT from server" " - marking file deleted and stale"); set_bit(AFS_VNODE_DELETED, &vnode->flags); ret = -ESTALE; } } _leave(" = %d", ret); return ret; } /* * prepare to perform part of a write to a page */ int afs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, struct page **pagep, void **fsdata) { struct afs_vnode *vnode = AFS_FS_I(file_inode(file)); struct page *page; struct key *key = afs_file_key(file); unsigned long priv; unsigned f, from = pos & (PAGE_SIZE - 1); unsigned t, to = from + len; pgoff_t index = pos >> PAGE_SHIFT; int ret; _enter("{%llx:%llu},{%lx},%u,%u", vnode->fid.vid, vnode->fid.vnode, index, from, to); /* We want to store information about how much of a page is altered in * page->private. */ BUILD_BUG_ON(PAGE_SIZE > 32768 && sizeof(page->private) < 8); page = grab_cache_page_write_begin(mapping, index, flags); if (!page) return -ENOMEM; if (!PageUptodate(page) && len != PAGE_SIZE) { ret = afs_fill_page(vnode, key, pos & PAGE_MASK, PAGE_SIZE, page); if (ret < 0) { unlock_page(page); put_page(page); _leave(" = %d [prep]", ret); return ret; } SetPageUptodate(page); } /* page won't leak in error case: it eventually gets cleaned off LRU */ *pagep = page; try_again: /* See if this page is already partially written in a way that we can * merge the new write with. */ t = f = 0; if (PagePrivate(page)) { priv = page_private(page); f = priv & AFS_PRIV_MAX; t = priv >> AFS_PRIV_SHIFT; ASSERTCMP(f, <=, t); } if (f != t) { if (PageWriteback(page)) { trace_afs_page_dirty(vnode, tracepoint_string("alrdy"), page->index, priv); goto flush_conflicting_write; } /* If the file is being filled locally, allow inter-write * spaces to be merged into writes. If it's not, only write * back what the user gives us. */ if (!test_bit(AFS_VNODE_NEW_CONTENT, &vnode->flags) && (to < f || from > t)) goto flush_conflicting_write; if (from < f) f = from; if (to > t) t = to; } else { f = from; t = to; } priv = (unsigned long)t << AFS_PRIV_SHIFT; priv |= f; trace_afs_page_dirty(vnode, tracepoint_string("begin"), page->index, priv); SetPagePrivate(page); set_page_private(page, priv); _leave(" = 0"); return 0; /* The previous write and this write aren't adjacent or overlapping, so * flush the page out. */ flush_conflicting_write: _debug("flush conflict"); ret = write_one_page(page); if (ret < 0) { _leave(" = %d", ret); return ret; } ret = lock_page_killable(page); if (ret < 0) { _leave(" = %d", ret); return ret; } goto try_again; } /* * finalise part of a write to a page */ int afs_write_end(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned copied, struct page *page, void *fsdata) { struct afs_vnode *vnode = AFS_FS_I(file_inode(file)); struct key *key = afs_file_key(file); loff_t i_size, maybe_i_size; int ret; _enter("{%llx:%llu},{%lx}", vnode->fid.vid, vnode->fid.vnode, page->index); maybe_i_size = pos + copied; i_size = i_size_read(&vnode->vfs_inode); if (maybe_i_size > i_size) { write_seqlock(&vnode->cb_lock); i_size = i_size_read(&vnode->vfs_inode); if (maybe_i_size > i_size) i_size_write(&vnode->vfs_inode, maybe_i_size); write_sequnlock(&vnode->cb_lock); } if (!PageUptodate(page)) { if (copied < len) { /* Try and load any missing data from the server. The * unmarshalling routine will take care of clearing any * bits that are beyond the EOF. */ ret = afs_fill_page(vnode, key, pos + copied, len - copied, page); if (ret < 0) goto out; } SetPageUptodate(page); } set_page_dirty(page); if (PageDirty(page)) _debug("dirtied"); ret = copied; out: unlock_page(page); put_page(page); return ret; } /* * kill all the pages in the given range */ static void afs_kill_pages(struct address_space *mapping, pgoff_t first, pgoff_t last) { struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct pagevec pv; unsigned count, loop; _enter("{%llx:%llu},%lx-%lx", vnode->fid.vid, vnode->fid.vnode, first, last); pagevec_init(&pv); do { _debug("kill %lx-%lx", first, last); count = last - first + 1; if (count > PAGEVEC_SIZE) count = PAGEVEC_SIZE; pv.nr = find_get_pages_contig(mapping, first, count, pv.pages); ASSERTCMP(pv.nr, ==, count); for (loop = 0; loop < count; loop++) { struct page *page = pv.pages[loop]; ClearPageUptodate(page); SetPageError(page); end_page_writeback(page); if (page->index >= first) first = page->index + 1; lock_page(page); generic_error_remove_page(mapping, page); unlock_page(page); } __pagevec_release(&pv); } while (first <= last); _leave(""); } /* * Redirty all the pages in a given range. */ static void afs_redirty_pages(struct writeback_control *wbc, struct address_space *mapping, pgoff_t first, pgoff_t last) { struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct pagevec pv; unsigned count, loop; _enter("{%llx:%llu},%lx-%lx", vnode->fid.vid, vnode->fid.vnode, first, last); pagevec_init(&pv); do { _debug("redirty %lx-%lx", first, last); count = last - first + 1; if (count > PAGEVEC_SIZE) count = PAGEVEC_SIZE; pv.nr = find_get_pages_contig(mapping, first, count, pv.pages); ASSERTCMP(pv.nr, ==, count); for (loop = 0; loop < count; loop++) { struct page *page = pv.pages[loop]; redirty_page_for_writepage(wbc, page); end_page_writeback(page); if (page->index >= first) first = page->index + 1; } __pagevec_release(&pv); } while (first <= last); _leave(""); } /* * completion of write to server */ static void afs_pages_written_back(struct afs_vnode *vnode, pgoff_t first, pgoff_t last) { struct pagevec pv; unsigned long priv; unsigned count, loop; _enter("{%llx:%llu},{%lx-%lx}", vnode->fid.vid, vnode->fid.vnode, first, last); pagevec_init(&pv); do { _debug("done %lx-%lx", first, last); count = last - first + 1; if (count > PAGEVEC_SIZE) count = PAGEVEC_SIZE; pv.nr = find_get_pages_contig(vnode->vfs_inode.i_mapping, first, count, pv.pages); ASSERTCMP(pv.nr, ==, count); for (loop = 0; loop < count; loop++) { priv = page_private(pv.pages[loop]); trace_afs_page_dirty(vnode, tracepoint_string("clear"), pv.pages[loop]->index, priv); set_page_private(pv.pages[loop], 0); end_page_writeback(pv.pages[loop]); } first += count; __pagevec_release(&pv); } while (first <= last); afs_prune_wb_keys(vnode); _leave(""); } /* * Find a key to use for the writeback. We cached the keys used to author the * writes on the vnode. *_wbk will contain the last writeback key used or NULL * and we need to start from there if it's set. */ static int afs_get_writeback_key(struct afs_vnode *vnode, struct afs_wb_key **_wbk) { struct afs_wb_key *wbk = NULL; struct list_head *p; int ret = -ENOKEY, ret2; spin_lock(&vnode->wb_lock); if (*_wbk) p = (*_wbk)->vnode_link.next; else p = vnode->wb_keys.next; while (p != &vnode->wb_keys) { wbk = list_entry(p, struct afs_wb_key, vnode_link); _debug("wbk %u", key_serial(wbk->key)); ret2 = key_validate(wbk->key); if (ret2 == 0) { refcount_inc(&wbk->usage); _debug("USE WB KEY %u", key_serial(wbk->key)); break; } wbk = NULL; if (ret == -ENOKEY) ret = ret2; p = p->next; } spin_unlock(&vnode->wb_lock); if (*_wbk) afs_put_wb_key(*_wbk); *_wbk = wbk; return 0; } static void afs_store_data_success(struct afs_operation *op) { struct afs_vnode *vnode = op->file[0].vnode; op->ctime = op->file[0].scb.status.mtime_client; afs_vnode_commit_status(op, &op->file[0]); if (op->error == 0) { afs_pages_written_back(vnode, op->store.first, op->store.last); afs_stat_v(vnode, n_stores); atomic_long_add((op->store.last * PAGE_SIZE + op->store.last_to) - (op->store.first * PAGE_SIZE + op->store.first_offset), &afs_v2net(vnode)->n_store_bytes); } } static const struct afs_operation_ops afs_store_data_operation = { .issue_afs_rpc = afs_fs_store_data, .issue_yfs_rpc = yfs_fs_store_data, .success = afs_store_data_success, }; /* * write to a file */ static int afs_store_data(struct address_space *mapping, pgoff_t first, pgoff_t last, unsigned offset, unsigned to) { struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct afs_operation *op; struct afs_wb_key *wbk = NULL; int ret; _enter("%s{%llx:%llu.%u},%lx,%lx,%x,%x", vnode->volume->name, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique, first, last, offset, to); ret = afs_get_writeback_key(vnode, &wbk); if (ret) { _leave(" = %d [no keys]", ret); return ret; } op = afs_alloc_operation(wbk->key, vnode->volume); if (IS_ERR(op)) { afs_put_wb_key(wbk); return -ENOMEM; } afs_op_set_vnode(op, 0, vnode); op->file[0].dv_delta = 1; op->store.mapping = mapping; op->store.first = first; op->store.last = last; op->store.first_offset = offset; op->store.last_to = to; op->mtime = vnode->vfs_inode.i_mtime; op->flags |= AFS_OPERATION_UNINTR; op->ops = &afs_store_data_operation; try_next_key: afs_begin_vnode_operation(op); afs_wait_for_operation(op); switch (op->error) { case -EACCES: case -EPERM: case -ENOKEY: case -EKEYEXPIRED: case -EKEYREJECTED: case -EKEYREVOKED: _debug("next"); ret = afs_get_writeback_key(vnode, &wbk); if (ret == 0) { key_put(op->key); op->key = key_get(wbk->key); goto try_next_key; } break; } afs_put_wb_key(wbk); _leave(" = %d", op->error); return afs_put_operation(op); } /* * Synchronously write back the locked page and any subsequent non-locked dirty * pages. */ static int afs_write_back_from_locked_page(struct address_space *mapping, struct writeback_control *wbc, struct page *primary_page, pgoff_t final_page) { struct afs_vnode *vnode = AFS_FS_I(mapping->host); struct page *pages[8], *page; unsigned long count, priv; unsigned n, offset, to, f, t; pgoff_t start, first, last; loff_t i_size, end; int loop, ret; _enter(",%lx", primary_page->index);