sparc: Increase portability of strncpy_from_user() implementation.
[deliverable/linux.git] / arch / sparc / lib / usercopy.c
CommitLineData
fb34035e 1#include <linux/module.h>
ff06dffb
DM
2#include <linux/uaccess.h>
3#include <linux/errno.h>
fb34035e
DM
4#include <linux/bug.h>
5
35c96460
DM
6#include <asm/byteorder.h>
7
fb34035e
DM
8void copy_from_user_overflow(void)
9{
10 WARN(1, "Buffer overflow detected!\n");
11}
12EXPORT_SYMBOL(copy_from_user_overflow);
ff06dffb
DM
13
14#define REPEAT_BYTE(x) ((~0ul / 0xff) * (x))
15
4efcac3a 16static inline long find_zero(unsigned long mask)
ff06dffb 17{
4efcac3a 18 long byte = 0;
35c96460
DM
19
20#ifdef __BIG_ENDIAN
ff06dffb 21#ifdef CONFIG_64BIT
4efcac3a
DM
22 if (mask >> 32)
23 mask >>= 32;
24 else
25 byte = 4;
ff06dffb 26#endif
4efcac3a
DM
27 if (mask >> 16)
28 mask >>= 16;
29 else
30 byte += 2;
31 return (mask >> 8) ? byte : byte + 1;
35c96460
DM
32#else
33#ifdef CONFIG_64BIT
34 if (!((unsigned int) mask)) {
35 mask >>= 32;
36 byte = 4;
37 }
38#endif
39 if (!(mask & 0xffff)) {
40 mask >>= 16;
41 byte += 2;
42 }
43 return (mask & 0xff) ? byte : byte + 1;
44#endif
ff06dffb
DM
45}
46
35c96460
DM
47#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
48#define IS_UNALIGNED(src, dst) 0
49#else
50#define IS_UNALIGNED(src, dst) \
51 (((long) dst | (long) src) & (sizeof(long) - 1))
52#endif
53
ff06dffb
DM
54/*
55 * Do a strncpy, return length of string without final '\0'.
56 * 'count' is the user-supplied count (return 'count' if we
57 * hit it), 'max' is the address space maximum (and we return
58 * -EFAULT if we hit it).
59 */
60static inline long do_strncpy_from_user(char *dst, const char __user *src, long count, unsigned long max)
61{
4efcac3a
DM
62 const unsigned long high_bits = REPEAT_BYTE(0xfe) + 1;
63 const unsigned long low_bits = REPEAT_BYTE(0x7f);
ff06dffb
DM
64 long res = 0;
65
66 /*
67 * Truncate 'max' to the user-specified limit, so that
68 * we only have one limit we need to check in the loop
69 */
70 if (max > count)
71 max = count;
72
35c96460 73 if (IS_UNALIGNED(src, dst))
ff06dffb
DM
74 goto byte_at_a_time;
75
76 while (max >= sizeof(unsigned long)) {
4efcac3a 77 unsigned long c, v, rhs;
ff06dffb
DM
78
79 /* Fall back to byte-at-a-time if we get a page fault */
80 if (unlikely(__get_user(c,(unsigned long __user *)(src+res))))
81 break;
4efcac3a
DM
82 rhs = c | low_bits;
83 v = (c + high_bits) & ~rhs;
ff06dffb 84 *(unsigned long *)(dst+res) = c;
4efcac3a 85 if (v) {
35c96460 86 v = (c & low_bits) + low_bits;
4efcac3a
DM
87 v = ~(v | rhs);
88 return res + find_zero(v);
89 }
ff06dffb
DM
90 res += sizeof(unsigned long);
91 max -= sizeof(unsigned long);
92 }
93
94byte_at_a_time:
95 while (max) {
96 char c;
97
98 if (unlikely(__get_user(c,src+res)))
99 return -EFAULT;
100 dst[res] = c;
101 if (!c)
102 return res;
103 res++;
104 max--;
105 }
106
107 /*
108 * Uhhuh. We hit 'max'. But was that the user-specified maximum
109 * too? If so, that's ok - we got as much as the user asked for.
110 */
111 if (res >= count)
112 return res;
113
114 /*
115 * Nope: we hit the address space limit, and we still had more
116 * characters the caller would have wanted. That's an EFAULT.
117 */
118 return -EFAULT;
119}
120
121/**
122 * strncpy_from_user: - Copy a NUL terminated string from userspace.
123 * @dst: Destination address, in kernel space. This buffer must be at
124 * least @count bytes long.
125 * @src: Source address, in user space.
126 * @count: Maximum number of bytes to copy, including the trailing NUL.
127 *
128 * Copies a NUL-terminated string from userspace to kernel space.
129 *
130 * On success, returns the length of the string (not including the trailing
131 * NUL).
132 *
133 * If access to userspace fails, returns -EFAULT (some data may have been
134 * copied).
135 *
136 * If @count is smaller than the length of the string, copies @count bytes
137 * and returns @count.
138 */
139long strncpy_from_user(char *dst, const char __user *src, long count)
140{
141 unsigned long max_addr, src_addr;
142
143 if (unlikely(count <= 0))
144 return 0;
145
35c96460 146 max_addr = user_addr_max();
ff06dffb
DM
147 src_addr = (unsigned long)src;
148 if (likely(src_addr < max_addr)) {
149 unsigned long max = max_addr - src_addr;
150 return do_strncpy_from_user(dst, src, count, max);
151 }
152 return -EFAULT;
153}
154EXPORT_SYMBOL(strncpy_from_user);
This page took 0.297981 seconds and 5 git commands to generate.