mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
15
arch/um/kernel/skas/Makefile
Normal file
15
arch/um/kernel/skas/Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
|
||||
# Licensed under the GPL
|
||||
#
|
||||
|
||||
obj-y := clone.o mmu.o process.o syscall.o uaccess.o
|
||||
|
||||
# clone.o is in the stub, so it can't be built with profiling
|
||||
# GCC hardened also auto-enables -fpic, but we need %ebx so it can't work ->
|
||||
# disable it
|
||||
|
||||
CFLAGS_clone.o := $(CFLAGS_NO_HARDENING)
|
||||
UNPROFILE_OBJS := clone.o
|
||||
|
||||
include arch/um/scripts/Makefile.rules
|
56
arch/um/kernel/skas/clone.c
Normal file
56
arch/um/kernel/skas/clone.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
|
||||
* Licensed under the GPL
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include <asm/unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <as-layout.h>
|
||||
#include <ptrace_user.h>
|
||||
#include <stub-data.h>
|
||||
#include <sysdep/stub.h>
|
||||
|
||||
/*
|
||||
* This is in a separate file because it needs to be compiled with any
|
||||
* extraneous gcc flags (-pg, -fprofile-arcs, -ftest-coverage) disabled
|
||||
*
|
||||
* Use UM_KERN_PAGE_SIZE instead of PAGE_SIZE because that calls getpagesize
|
||||
* on some systems.
|
||||
*/
|
||||
|
||||
void __attribute__ ((__section__ (".__syscall_stub")))
|
||||
stub_clone_handler(void)
|
||||
{
|
||||
struct stub_data *data = (struct stub_data *) STUB_DATA;
|
||||
long err;
|
||||
|
||||
err = stub_syscall2(__NR_clone, CLONE_PARENT | CLONE_FILES | SIGCHLD,
|
||||
STUB_DATA + UM_KERN_PAGE_SIZE / 2 - sizeof(void *));
|
||||
if (err != 0)
|
||||
goto out;
|
||||
|
||||
err = stub_syscall4(__NR_ptrace, PTRACE_TRACEME, 0, 0, 0);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = stub_syscall3(__NR_setitimer, ITIMER_VIRTUAL,
|
||||
(long) &data->timer, 0);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
remap_stack(data->fd, data->offset);
|
||||
goto done;
|
||||
|
||||
out:
|
||||
/*
|
||||
* save current result.
|
||||
* Parent: pid;
|
||||
* child: retcode of mmap already saved and it jumps around this
|
||||
* assignment
|
||||
*/
|
||||
data->err = err;
|
||||
done:
|
||||
trap_myself();
|
||||
}
|
178
arch/um/kernel/skas/mmu.c
Normal file
178
arch/um/kernel/skas/mmu.c
Normal file
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
|
||||
* Licensed under the GPL
|
||||
*/
|
||||
|
||||
#include <linux/mm.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/pgalloc.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <as-layout.h>
|
||||
#include <os.h>
|
||||
#include <skas.h>
|
||||
|
||||
extern int __syscall_stub_start;
|
||||
|
||||
static int init_stub_pte(struct mm_struct *mm, unsigned long proc,
|
||||
unsigned long kernel)
|
||||
{
|
||||
pgd_t *pgd;
|
||||
pud_t *pud;
|
||||
pmd_t *pmd;
|
||||
pte_t *pte;
|
||||
|
||||
pgd = pgd_offset(mm, proc);
|
||||
pud = pud_alloc(mm, pgd, proc);
|
||||
if (!pud)
|
||||
goto out;
|
||||
|
||||
pmd = pmd_alloc(mm, pud, proc);
|
||||
if (!pmd)
|
||||
goto out_pmd;
|
||||
|
||||
pte = pte_alloc_map(mm, NULL, pmd, proc);
|
||||
if (!pte)
|
||||
goto out_pte;
|
||||
|
||||
*pte = mk_pte(virt_to_page(kernel), __pgprot(_PAGE_PRESENT));
|
||||
*pte = pte_mkread(*pte);
|
||||
return 0;
|
||||
|
||||
out_pte:
|
||||
pmd_free(mm, pmd);
|
||||
out_pmd:
|
||||
pud_free(mm, pud);
|
||||
out:
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
int init_new_context(struct task_struct *task, struct mm_struct *mm)
|
||||
{
|
||||
struct mm_context *from_mm = NULL;
|
||||
struct mm_context *to_mm = &mm->context;
|
||||
unsigned long stack = 0;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
if (skas_needs_stub) {
|
||||
stack = get_zeroed_page(GFP_KERNEL);
|
||||
if (stack == 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
to_mm->id.stack = stack;
|
||||
if (current->mm != NULL && current->mm != &init_mm)
|
||||
from_mm = ¤t->mm->context;
|
||||
|
||||
if (proc_mm) {
|
||||
ret = new_mm(stack);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "init_new_context_skas - "
|
||||
"new_mm failed, errno = %d\n", ret);
|
||||
goto out_free;
|
||||
}
|
||||
to_mm->id.u.mm_fd = ret;
|
||||
}
|
||||
else {
|
||||
if (from_mm)
|
||||
to_mm->id.u.pid = copy_context_skas0(stack,
|
||||
from_mm->id.u.pid);
|
||||
else to_mm->id.u.pid = start_userspace(stack);
|
||||
|
||||
if (to_mm->id.u.pid < 0) {
|
||||
ret = to_mm->id.u.pid;
|
||||
goto out_free;
|
||||
}
|
||||
}
|
||||
|
||||
ret = init_new_ldt(to_mm, from_mm);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "init_new_context_skas - init_ldt"
|
||||
" failed, errno = %d\n", ret);
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
if (to_mm->id.stack != 0)
|
||||
free_page(to_mm->id.stack);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void uml_setup_stubs(struct mm_struct *mm)
|
||||
{
|
||||
int err, ret;
|
||||
|
||||
if (!skas_needs_stub)
|
||||
return;
|
||||
|
||||
ret = init_stub_pte(mm, STUB_CODE,
|
||||
(unsigned long) &__syscall_stub_start);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = init_stub_pte(mm, STUB_DATA, mm->context.id.stack);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
mm->context.stub_pages[0] = virt_to_page(&__syscall_stub_start);
|
||||
mm->context.stub_pages[1] = virt_to_page(mm->context.id.stack);
|
||||
|
||||
/* dup_mmap already holds mmap_sem */
|
||||
err = install_special_mapping(mm, STUB_START, STUB_END - STUB_START,
|
||||
VM_READ | VM_MAYREAD | VM_EXEC |
|
||||
VM_MAYEXEC | VM_DONTCOPY | VM_PFNMAP,
|
||||
mm->context.stub_pages);
|
||||
if (err) {
|
||||
printk(KERN_ERR "install_special_mapping returned %d\n", err);
|
||||
goto out;
|
||||
}
|
||||
return;
|
||||
|
||||
out:
|
||||
force_sigsegv(SIGSEGV, current);
|
||||
}
|
||||
|
||||
void arch_exit_mmap(struct mm_struct *mm)
|
||||
{
|
||||
pte_t *pte;
|
||||
|
||||
pte = virt_to_pte(mm, STUB_CODE);
|
||||
if (pte != NULL)
|
||||
pte_clear(mm, STUB_CODE, pte);
|
||||
|
||||
pte = virt_to_pte(mm, STUB_DATA);
|
||||
if (pte == NULL)
|
||||
return;
|
||||
|
||||
pte_clear(mm, STUB_DATA, pte);
|
||||
}
|
||||
|
||||
void destroy_context(struct mm_struct *mm)
|
||||
{
|
||||
struct mm_context *mmu = &mm->context;
|
||||
|
||||
if (proc_mm)
|
||||
os_close_file(mmu->id.u.mm_fd);
|
||||
else {
|
||||
/*
|
||||
* If init_new_context wasn't called, this will be
|
||||
* zero, resulting in a kill(0), which will result in the
|
||||
* whole UML suddenly dying. Also, cover negative and
|
||||
* 1 cases, since they shouldn't happen either.
|
||||
*/
|
||||
if (mmu->id.u.pid < 2) {
|
||||
printk(KERN_ERR "corrupt mm_context - pid = %d\n",
|
||||
mmu->id.u.pid);
|
||||
return;
|
||||
}
|
||||
os_kill_ptraced_process(mmu->id.u.pid, 1);
|
||||
}
|
||||
|
||||
if (skas_needs_stub)
|
||||
free_page(mmu->id.stack);
|
||||
|
||||
free_ldt(mmu);
|
||||
}
|
81
arch/um/kernel/skas/process.c
Normal file
81
arch/um/kernel/skas/process.c
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
|
||||
* Licensed under the GPL
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <as-layout.h>
|
||||
#include <kern.h>
|
||||
#include <os.h>
|
||||
#include <skas.h>
|
||||
|
||||
int new_mm(unsigned long stack)
|
||||
{
|
||||
int fd, err;
|
||||
|
||||
fd = os_open_file("/proc/mm", of_cloexec(of_write(OPENFLAGS())), 0);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
if (skas_needs_stub) {
|
||||
err = map_stub_pages(fd, STUB_CODE, STUB_DATA, stack);
|
||||
if (err) {
|
||||
os_close_file(fd);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
extern void start_kernel(void);
|
||||
|
||||
static int __init start_kernel_proc(void *unused)
|
||||
{
|
||||
int pid;
|
||||
|
||||
block_signals();
|
||||
pid = os_getpid();
|
||||
|
||||
cpu_tasks[0].pid = pid;
|
||||
cpu_tasks[0].task = current;
|
||||
#ifdef CONFIG_SMP
|
||||
init_cpu_online(get_cpu_mask(0));
|
||||
#endif
|
||||
start_kernel();
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern int userspace_pid[];
|
||||
|
||||
extern char cpu0_irqstack[];
|
||||
|
||||
int __init start_uml(void)
|
||||
{
|
||||
stack_protections((unsigned long) &cpu0_irqstack);
|
||||
set_sigstack(cpu0_irqstack, THREAD_SIZE);
|
||||
if (proc_mm) {
|
||||
userspace_pid[0] = start_userspace(0);
|
||||
if (userspace_pid[0] < 0) {
|
||||
printf("start_uml - start_userspace returned %d\n",
|
||||
userspace_pid[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
init_new_thread_signals();
|
||||
|
||||
init_task.thread.request.u.thread.proc = start_kernel_proc;
|
||||
init_task.thread.request.u.thread.arg = NULL;
|
||||
return start_idle_thread(task_stack_page(&init_task),
|
||||
&init_task.thread.switch_buf);
|
||||
}
|
||||
|
||||
unsigned long current_stub_stack(void)
|
||||
{
|
||||
if (current->mm == NULL)
|
||||
return 0;
|
||||
|
||||
return current->mm->context.id.stack;
|
||||
}
|
40
arch/um/kernel/skas/syscall.c
Normal file
40
arch/um/kernel/skas/syscall.c
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
|
||||
* Licensed under the GPL
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/ptrace.h>
|
||||
#include <kern_util.h>
|
||||
#include <sysdep/ptrace.h>
|
||||
#include <sysdep/syscalls.h>
|
||||
|
||||
extern int syscall_table_size;
|
||||
#define NR_SYSCALLS (syscall_table_size / sizeof(void *))
|
||||
|
||||
void handle_syscall(struct uml_pt_regs *r)
|
||||
{
|
||||
struct pt_regs *regs = container_of(r, struct pt_regs, regs);
|
||||
long result;
|
||||
int syscall;
|
||||
|
||||
syscall_trace_enter(regs);
|
||||
|
||||
/*
|
||||
* This should go in the declaration of syscall, but when I do that,
|
||||
* strace -f -c bash -c 'ls ; ls' breaks, sometimes not tracing
|
||||
* children at all, sometimes hanging when bash doesn't see the first
|
||||
* ls exit.
|
||||
* The assembly looks functionally the same to me. This is
|
||||
* gcc version 4.0.1 20050727 (Red Hat 4.0.1-5)
|
||||
* in case it's a compiler bug.
|
||||
*/
|
||||
syscall = UPT_SYSCALL_NR(r);
|
||||
if ((syscall >= NR_SYSCALLS) || (syscall < 0))
|
||||
result = -ENOSYS;
|
||||
else result = EXECUTE_SYSCALL(syscall, regs);
|
||||
|
||||
PT_REGS_SET_SYSCALL_RETURN(regs, result);
|
||||
|
||||
syscall_trace_leave(regs);
|
||||
}
|
259
arch/um/kernel/skas/uaccess.c
Normal file
259
arch/um/kernel/skas/uaccess.c
Normal file
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Copyright (C) 2002 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
|
||||
* Licensed under the GPL
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <kern_util.h>
|
||||
#include <os.h>
|
||||
|
||||
pte_t *virt_to_pte(struct mm_struct *mm, unsigned long addr)
|
||||
{
|
||||
pgd_t *pgd;
|
||||
pud_t *pud;
|
||||
pmd_t *pmd;
|
||||
|
||||
if (mm == NULL)
|
||||
return NULL;
|
||||
|
||||
pgd = pgd_offset(mm, addr);
|
||||
if (!pgd_present(*pgd))
|
||||
return NULL;
|
||||
|
||||
pud = pud_offset(pgd, addr);
|
||||
if (!pud_present(*pud))
|
||||
return NULL;
|
||||
|
||||
pmd = pmd_offset(pud, addr);
|
||||
if (!pmd_present(*pmd))
|
||||
return NULL;
|
||||
|
||||
return pte_offset_kernel(pmd, addr);
|
||||
}
|
||||
|
||||
static pte_t *maybe_map(unsigned long virt, int is_write)
|
||||
{
|
||||
pte_t *pte = virt_to_pte(current->mm, virt);
|
||||
int err, dummy_code;
|
||||
|
||||
if ((pte == NULL) || !pte_present(*pte) ||
|
||||
(is_write && !pte_write(*pte))) {
|
||||
err = handle_page_fault(virt, 0, is_write, 1, &dummy_code);
|
||||
if (err)
|
||||
return NULL;
|
||||
pte = virt_to_pte(current->mm, virt);
|
||||
}
|
||||
if (!pte_present(*pte))
|
||||
pte = NULL;
|
||||
|
||||
return pte;
|
||||
}
|
||||
|
||||
static int do_op_one_page(unsigned long addr, int len, int is_write,
|
||||
int (*op)(unsigned long addr, int len, void *arg), void *arg)
|
||||
{
|
||||
jmp_buf buf;
|
||||
struct page *page;
|
||||
pte_t *pte;
|
||||
int n, faulted;
|
||||
|
||||
pte = maybe_map(addr, is_write);
|
||||
if (pte == NULL)
|
||||
return -1;
|
||||
|
||||
page = pte_page(*pte);
|
||||
addr = (unsigned long) kmap_atomic(page) +
|
||||
(addr & ~PAGE_MASK);
|
||||
|
||||
current->thread.fault_catcher = &buf;
|
||||
|
||||
faulted = UML_SETJMP(&buf);
|
||||
if (faulted == 0)
|
||||
n = (*op)(addr, len, arg);
|
||||
else
|
||||
n = -1;
|
||||
|
||||
current->thread.fault_catcher = NULL;
|
||||
|
||||
kunmap_atomic((void *)addr);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int buffer_op(unsigned long addr, int len, int is_write,
|
||||
int (*op)(unsigned long, int, void *), void *arg)
|
||||
{
|
||||
int size, remain, n;
|
||||
|
||||
size = min(PAGE_ALIGN(addr) - addr, (unsigned long) len);
|
||||
remain = len;
|
||||
|
||||
n = do_op_one_page(addr, size, is_write, op, arg);
|
||||
if (n != 0) {
|
||||
remain = (n < 0 ? remain : 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
addr += size;
|
||||
remain -= size;
|
||||
if (remain == 0)
|
||||
goto out;
|
||||
|
||||
while (addr < ((addr + remain) & PAGE_MASK)) {
|
||||
n = do_op_one_page(addr, PAGE_SIZE, is_write, op, arg);
|
||||
if (n != 0) {
|
||||
remain = (n < 0 ? remain : 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
addr += PAGE_SIZE;
|
||||
remain -= PAGE_SIZE;
|
||||
}
|
||||
if (remain == 0)
|
||||
goto out;
|
||||
|
||||
n = do_op_one_page(addr, remain, is_write, op, arg);
|
||||
if (n != 0) {
|
||||
remain = (n < 0 ? remain : 0);
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out:
|
||||
return remain;
|
||||
}
|
||||
|
||||
static int copy_chunk_from_user(unsigned long from, int len, void *arg)
|
||||
{
|
||||
unsigned long *to_ptr = arg, to = *to_ptr;
|
||||
|
||||
memcpy((void *) to, (void *) from, len);
|
||||
*to_ptr += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int copy_from_user(void *to, const void __user *from, int n)
|
||||
{
|
||||
if (segment_eq(get_fs(), KERNEL_DS)) {
|
||||
memcpy(to, (__force void*)from, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return access_ok(VERIFY_READ, from, n) ?
|
||||
buffer_op((unsigned long) from, n, 0, copy_chunk_from_user, &to):
|
||||
n;
|
||||
}
|
||||
EXPORT_SYMBOL(copy_from_user);
|
||||
|
||||
static int copy_chunk_to_user(unsigned long to, int len, void *arg)
|
||||
{
|
||||
unsigned long *from_ptr = arg, from = *from_ptr;
|
||||
|
||||
memcpy((void *) to, (void *) from, len);
|
||||
*from_ptr += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int copy_to_user(void __user *to, const void *from, int n)
|
||||
{
|
||||
if (segment_eq(get_fs(), KERNEL_DS)) {
|
||||
memcpy((__force void *) to, from, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return access_ok(VERIFY_WRITE, to, n) ?
|
||||
buffer_op((unsigned long) to, n, 1, copy_chunk_to_user, &from) :
|
||||
n;
|
||||
}
|
||||
EXPORT_SYMBOL(copy_to_user);
|
||||
|
||||
static int strncpy_chunk_from_user(unsigned long from, int len, void *arg)
|
||||
{
|
||||
char **to_ptr = arg, *to = *to_ptr;
|
||||
int n;
|
||||
|
||||
strncpy(to, (void *) from, len);
|
||||
n = strnlen(to, len);
|
||||
*to_ptr += n;
|
||||
|
||||
if (n < len)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int strncpy_from_user(char *dst, const char __user *src, int count)
|
||||
{
|
||||
int n;
|
||||
char *ptr = dst;
|
||||
|
||||
if (segment_eq(get_fs(), KERNEL_DS)) {
|
||||
strncpy(dst, (__force void *) src, count);
|
||||
return strnlen(dst, count);
|
||||
}
|
||||
|
||||
if (!access_ok(VERIFY_READ, src, 1))
|
||||
return -EFAULT;
|
||||
|
||||
n = buffer_op((unsigned long) src, count, 0, strncpy_chunk_from_user,
|
||||
&ptr);
|
||||
if (n != 0)
|
||||
return -EFAULT;
|
||||
return strnlen(dst, count);
|
||||
}
|
||||
EXPORT_SYMBOL(strncpy_from_user);
|
||||
|
||||
static int clear_chunk(unsigned long addr, int len, void *unused)
|
||||
{
|
||||
memset((void *) addr, 0, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __clear_user(void __user *mem, int len)
|
||||
{
|
||||
return buffer_op((unsigned long) mem, len, 1, clear_chunk, NULL);
|
||||
}
|
||||
|
||||
int clear_user(void __user *mem, int len)
|
||||
{
|
||||
if (segment_eq(get_fs(), KERNEL_DS)) {
|
||||
memset((__force void*)mem, 0, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return access_ok(VERIFY_WRITE, mem, len) ?
|
||||
buffer_op((unsigned long) mem, len, 1, clear_chunk, NULL) : len;
|
||||
}
|
||||
EXPORT_SYMBOL(clear_user);
|
||||
|
||||
static int strnlen_chunk(unsigned long str, int len, void *arg)
|
||||
{
|
||||
int *len_ptr = arg, n;
|
||||
|
||||
n = strnlen((void *) str, len);
|
||||
*len_ptr += n;
|
||||
|
||||
if (n < len)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int strnlen_user(const void __user *str, int len)
|
||||
{
|
||||
int count = 0, n;
|
||||
|
||||
if (segment_eq(get_fs(), KERNEL_DS))
|
||||
return strnlen((__force char*)str, len) + 1;
|
||||
|
||||
n = buffer_op((unsigned long) str, len, 0, strnlen_chunk, &count);
|
||||
if (n == 0)
|
||||
return count + 1;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(strnlen_user);
|
Loading…
Add table
Add a link
Reference in a new issue