1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
/* -*- linux-c -*-
* Copy from user space functions
* Copyright (C) 2012 Red Hat Inc.
*
* This file is part of systemtap, and is free software. You can
* redistribute it and/or modify it under the terms of the GNU General
* Public License (GPL); either version 2, or (at your option) any
* later version.
*/
#ifndef _STAPDYN_COPY_C_
#define _STAPDYN_COPY_C_
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include "stp_string.c"
static int _stp_mem_fd = -1;
static inline __must_check ssize_t __self_readv(const struct iovec *lvec,
unsigned long liovcnt,
const struct iovec *rvec,
unsigned long riovcnt)
{
#if !__GLIBC_PREREQ(2, 15)
#ifdef __NR_process_vm_readv
#define process_vm_readv(...) \
syscall(__NR_process_vm_readv, __VA_ARGS__)
#else
#define process_vm_readv(...) \
({ (void)lvec; (void)liovcnt; \
(void)rvec; (void)riovcnt; \
errno = ENOSYS; -1 })
#endif
#endif
return process_vm_readv(getpid(), lvec, liovcnt, rvec, riovcnt, 0UL);
}
static inline __must_check ssize_t __self_writev(const struct iovec *lvec,
unsigned long liovcnt,
const struct iovec *rvec,
unsigned long riovcnt)
{
#if !__GLIBC_PREREQ(2, 15)
#ifdef __NR_process_vm_writev
#define process_vm_writev(...) \
syscall(__NR_process_vm_writev, __VA_ARGS__)
#else
#define process_vm_writev(...) \
({ (void)lvec; (void)liovcnt; \
(void)rvec; (void)riovcnt; \
errno = ENOSYS; -1 })
#endif
#endif
return process_vm_writev(getpid(), lvec, liovcnt, rvec, riovcnt, 0UL);
}
static int _stp_copy_init(void)
{
/* Try a no-op process_vm_readv/writev to make sure they're available,
* esp. not ENOSYS, then we don't need to bother /proc/self/mem. */
if ((__self_readv(NULL, 0, NULL, 0) == 0) &&
(__self_writev(NULL, 0, NULL, 0) == 0))
return 0;
_stp_mem_fd = open("/proc/self/mem", O_RDWR /*| O_LARGEFILE*/);
if (_stp_mem_fd < 0)
return -errno;
fcntl(_stp_mem_fd, F_SETFD, FD_CLOEXEC);
return 0;
}
static void _stp_copy_destroy(void)
{
if (_stp_mem_fd >= 0) {
close (_stp_mem_fd);
_stp_mem_fd = -1;
}
}
static inline __must_check long __copy_from_user(void *to,
const void __user * from, unsigned long n)
{
int rc = 0;
if (_stp_mem_fd >= 0) {
/* pread is like lseek+read, without racing other threads. */
if (pread(_stp_mem_fd, to, n, (off_t)(uintptr_t)from) != n)
rc = -EFAULT;
} else {
struct iovec lvec = { .iov_base = to, .iov_len = n };
struct iovec rvec = { .iov_base = (void *)from, .iov_len = n };
if (__self_readv(&lvec, 1, &rvec, 1) != n)
rc = -EFAULT;
}
return rc;
}
static inline __must_check long __copy_to_user(void *to, const void *from,
unsigned long n)
{
int rc = 0;
if (_stp_mem_fd >= 0) {
/* pwrite is like lseek+write, without racing other threads. */
/* NB: some kernels will refuse to write /proc/self/mem */
if (pwrite(_stp_mem_fd, from, n, (off_t)(uintptr_t)to) != n)
rc = -EFAULT;
} else {
struct iovec lvec = { .iov_base = (void *)from, .iov_len = n };
struct iovec rvec = { .iov_base = to, .iov_len = n };
if (__self_writev(&lvec, 1, &rvec, 1) != n)
rc = -EFAULT;
}
return rc;
}
static long
_stp_strncpy_from_user(char *dst, const char *src, long count)
{
if (count <= 0)
return -EINVAL;
/* Reads are batched on aligned 4k boundaries, approximately
* page size, to reduce the number of pread syscalls. It will
* likely read past the terminating '\0', but shouldn't fault.
* NB: We shouldn't try to read the entire 'count' at once, in
* case some small string is already near the end of its page.
*/
long i = 0;
while (i < count) {
long n = 0x1000 - ((long)(src + i) & 0xFFF);
n = min(n, count - i);
if (__copy_from_user(dst + i, src + i, n))
return -EFAULT;
char *dst0 = memchr(dst + i, 0, n);
if (dst0 != NULL)
return (dst0 - dst);
i += n;
}
dst[i - 1] = 0;
return i - 1;
}
static unsigned long _stp_copy_from_user(char *dst, const char *src, unsigned long count)
{
if (count && __copy_from_user(dst, src, count) == 0)
return 0;
return count;
}
#endif /* _STAPDYN_COPY_C_ */
|