Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268/* * Copyright (c) 2006 Oracle. All rights reserved. * * This software is available to you under a choice of one of two * licenses. You may choose to be licensed under the terms of the GNU * General Public License (GPL) Version 2, available from the file * COPYING in the main directory of this source tree, or the * OpenIB.org BSD license below: * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */#include <linux/percpu.h>#include <linux/seq_file.h>#include <linux/srcu.h>#include <linux/slab.h>#include <linux/proc_fs.h>#include <linux/export.h>#include <linux/uio.h>
#include "rds.h"
/* * This file implements a getsockopt() call which copies a set of fixed * sized structs into a user-specified buffer as a means of providing * read-only information about RDS. * * For a given information source there are a given number of fixed sized * structs at a given time. The structs are only copied if the user-specified * buffer is big enough. The destination pages that make up the buffer * are pinned for the duration of the copy. * * This gives us the following benefits: * * - simple implementation, no copy "position" across multiple calls * - consistent snapshot of an info source * - atomic copy works well with whatever locking info source has * - one portable tool to get rds info across implementations * - long-lived tool can get info without allocating * * at the following costs: * * - info source copy must be pinned, may be "large" */
struct rds_info_iterator { struct page **pages; void *addr; unsigned long offset;};
DEFINE_STATIC_SRCU(rds_info_srcu);static DEFINE_SPINLOCK(rds_info_lock);static rds_info_func rds_info_funcs[RDS_INFO_LAST - RDS_INFO_FIRST + 1];
void rds_info_register_func(int optname, rds_info_func func){ int offset = optname - RDS_INFO_FIRST;
BUG_ON(optname < RDS_INFO_FIRST || optname > RDS_INFO_LAST);
spin_lock(&rds_info_lock); if (WARN_ON_ONCE(rds_info_funcs[offset])) { spin_unlock(&rds_info_lock); return; } WRITE_ONCE(rds_info_funcs[offset], func); spin_unlock(&rds_info_lock);}EXPORT_SYMBOL_GPL(rds_info_register_func);
void rds_info_deregister_func(int optname, rds_info_func func){ int offset = optname - RDS_INFO_FIRST;
BUG_ON(optname < RDS_INFO_FIRST || optname > RDS_INFO_LAST);
spin_lock(&rds_info_lock); if (WARN_ON_ONCE(rds_info_funcs[offset] != func)) { spin_unlock(&rds_info_lock); return; } WRITE_ONCE(rds_info_funcs[offset], NULL); spin_unlock(&rds_info_lock); synchronize_srcu(&rds_info_srcu);}EXPORT_SYMBOL_GPL(rds_info_deregister_func);
/* * Typically we hold an atomic kmap across multiple rds_info_copy() calls * because the kmap is so expensive. This must be called before using blocking * operations while holding the mapping and as the iterator is torn down. */void rds_info_iter_unmap(struct rds_info_iterator *iter){ if (iter->addr) { kunmap_atomic(iter->addr); iter->addr = NULL; }}
/* * get_user_pages() called flush_dcache_page() on the pages for us. */void rds_info_copy(struct rds_info_iterator *iter, void *data, unsigned long bytes){ unsigned long this;
while (bytes) { if (!iter->addr) iter->addr = kmap_atomic(*iter->pages);
this = min(bytes, PAGE_SIZE - iter->offset);
rdsdebug("page %p addr %p offset %lu this %lu data %p " "bytes %lu\n", *iter->pages, iter->addr, iter->offset, this, data, bytes);
memcpy(iter->addr + iter->offset, data, this);
data += this; bytes -= this; iter->offset += this;
if (iter->offset == PAGE_SIZE) { kunmap_atomic(iter->addr); iter->addr = NULL; iter->offset = 0; iter->pages++; } }}EXPORT_SYMBOL_GPL(rds_info_copy);
/* * @opt->iter_out describes the buffer that the information snapshot will be * copied into, and @opt->optlen is the size of that buffer on input. On * output @opt->optlen is set to the size of the requested snapshot in bytes. * * This function returns -errno if there is a failure, particularly -ENOSPC * if the given buffer was not large enough to fit the snapshot. On success * it returns the positive number of bytes of each array element in the * snapshot. */int rds_info_getsockopt(struct socket *sock, int optname, sockopt_t *opt){ struct rds_info_iterator iter; struct rds_info_lengths lens; unsigned long nr_pages = 0; rds_info_func func; struct page **pages = NULL; size_t offset0 = 0; int srcu_idx; int npages = 0; int ret; int len; int total;
len = opt->optlen;
/* check for all kinds of wrapping and the like */ if (len < 0 || len > INT_MAX - PAGE_SIZE + 1) { ret = -EINVAL; goto out; }
/* The info producers write into the pages with kmap_atomic() while * holding a spinlock, so they need a genuine page-backed user buffer. */ if (!user_backed_iter(&opt->iter_out)) { ret = -EOPNOTSUPP; goto out; }
/* a 0 len call is just trying to probe its length */ if (len == 0) goto call_func;
/* * Preallocate the page array and pass it in so that * iov_iter_extract_pages() fills it in place rather than allocating * one for us. Handing it a non-NULL array keeps ownership of the * array with us on every return path, instead of depending on the * iterator code to allocate and hand it back. */ npages = iov_iter_npages(&opt->iter_out, INT_MAX); pages = kvmalloc_objs(*pages, npages); if (!pages) { ret = -ENOMEM; goto out; }
ret = iov_iter_extract_pages(&opt->iter_out, &pages, len, npages, 0, &offset0); if (ret < 0) goto out; nr_pages = DIV_ROUND_UP(offset0 + ret, PAGE_SIZE); if (ret != len) { ret = -EAGAIN; /* XXX ? */ goto out; }
rdsdebug("len %d nr_pages %lu\n", len, nr_pages);
call_func: srcu_idx = srcu_read_lock(&rds_info_srcu); func = READ_ONCE(rds_info_funcs[optname - RDS_INFO_FIRST]); if (!func) { srcu_read_unlock(&rds_info_srcu, srcu_idx); ret = -ENOPROTOOPT; goto out; }
iter.pages = pages; iter.addr = NULL; iter.offset = offset0;
func(sock, len, &iter, &lens); srcu_read_unlock(&rds_info_srcu, srcu_idx); BUG_ON(lens.each == 0);
total = lens.nr * lens.each;
rds_info_iter_unmap(&iter);
if (total > len) { len = total; ret = -ENOSPC; } else { len = total; ret = lens.each; }
opt->optlen = len;
out: /* * iov_iter_extract_pages() pins only user-backed (ubuf) iters; * iov_iter_extract_will_pin() reports whether an unpin is owed here. */ if (pages && iov_iter_extract_will_pin(&opt->iter_out)) unpin_user_pages_dirty_lock(pages, nr_pages, true); kvfree(pages);
return ret;}