diff options
author | Martin Schwidefsky <schwidefsky@de.ibm.com> | 2011-01-05 06:48:10 -0500 |
---|---|---|
committer | Martin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com> | 2011-01-05 06:47:31 -0500 |
commit | 5e9a26928f550157563cfc06ce12c4ae121a02ec (patch) | |
tree | fc58668f8c6151a5f58c0430f92a0691d727af42 /arch/s390/kernel/ptrace.c | |
parent | da7f51c11d5fedca9ba779ee220063ccb4f0a27e (diff) |
[S390] ptrace cleanup
Overhaul program event recording and the code dealing with the ptrace
user space interface.
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'arch/s390/kernel/ptrace.c')
-rw-r--r-- | arch/s390/kernel/ptrace.c | 306 |
1 files changed, 187 insertions, 119 deletions
diff --git a/arch/s390/kernel/ptrace.c b/arch/s390/kernel/ptrace.c index 019bb714db49..ef86ad243986 100644 --- a/arch/s390/kernel/ptrace.c +++ b/arch/s390/kernel/ptrace.c | |||
@@ -1,25 +1,9 @@ | |||
1 | /* | 1 | /* |
2 | * arch/s390/kernel/ptrace.c | 2 | * Ptrace user space interface. |
3 | * | 3 | * |
4 | * S390 version | 4 | * Copyright IBM Corp. 1999,2010 |
5 | * Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation | 5 | * Author(s): Denis Joseph Barrow |
6 | * Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com), | ||
7 | * Martin Schwidefsky (schwidefsky@de.ibm.com) | 6 | * Martin Schwidefsky (schwidefsky@de.ibm.com) |
8 | * | ||
9 | * Based on PowerPC version | ||
10 | * Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org) | ||
11 | * | ||
12 | * Derived from "arch/m68k/kernel/ptrace.c" | ||
13 | * Copyright (C) 1994 by Hamish Macdonald | ||
14 | * Taken from linux/kernel/ptrace.c and modified for M680x0. | ||
15 | * linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds | ||
16 | * | ||
17 | * Modified by Cort Dougan (cort@cs.nmt.edu) | ||
18 | * | ||
19 | * | ||
20 | * This file is subject to the terms and conditions of the GNU General | ||
21 | * Public License. See the file README.legal in the main directory of | ||
22 | * this archive for more details. | ||
23 | */ | 7 | */ |
24 | 8 | ||
25 | #include <linux/kernel.h> | 9 | #include <linux/kernel.h> |
@@ -61,76 +45,58 @@ enum s390_regset { | |||
61 | REGSET_GENERAL_EXTENDED, | 45 | REGSET_GENERAL_EXTENDED, |
62 | }; | 46 | }; |
63 | 47 | ||
64 | static void | 48 | void update_per_regs(struct task_struct *task) |
65 | FixPerRegisters(struct task_struct *task) | ||
66 | { | 49 | { |
67 | struct pt_regs *regs; | 50 | static const struct per_regs per_single_step = { |
68 | per_struct *per_info; | 51 | .control = PER_EVENT_IFETCH, |
69 | per_cr_words cr_words; | 52 | .start = 0, |
70 | 53 | .end = PSW_ADDR_INSN, | |
71 | regs = task_pt_regs(task); | 54 | }; |
72 | per_info = (per_struct *) &task->thread.per_info; | 55 | struct pt_regs *regs = task_pt_regs(task); |
73 | per_info->control_regs.bits.em_instruction_fetch = | 56 | struct thread_struct *thread = &task->thread; |
74 | per_info->single_step | per_info->instruction_fetch; | 57 | const struct per_regs *new; |
75 | 58 | struct per_regs old; | |
76 | if (per_info->single_step) { | 59 | |
77 | per_info->control_regs.bits.starting_addr = 0; | 60 | /* TIF_SINGLE_STEP overrides the user specified PER registers. */ |
78 | #ifdef CONFIG_COMPAT | 61 | new = test_tsk_thread_flag(task, TIF_SINGLE_STEP) ? |
79 | if (is_compat_task()) | 62 | &per_single_step : &thread->per_user; |
80 | per_info->control_regs.bits.ending_addr = 0x7fffffffUL; | 63 | |
81 | else | 64 | /* Take care of the PER enablement bit in the PSW. */ |
82 | #endif | 65 | if (!(new->control & PER_EVENT_MASK)) { |
83 | per_info->control_regs.bits.ending_addr = PSW_ADDR_INSN; | ||
84 | } else { | ||
85 | per_info->control_regs.bits.starting_addr = | ||
86 | per_info->starting_addr; | ||
87 | per_info->control_regs.bits.ending_addr = | ||
88 | per_info->ending_addr; | ||
89 | } | ||
90 | /* | ||
91 | * if any of the control reg tracing bits are on | ||
92 | * we switch on per in the psw | ||
93 | */ | ||
94 | if (per_info->control_regs.words.cr[0] & PER_EM_MASK) | ||
95 | regs->psw.mask |= PSW_MASK_PER; | ||
96 | else | ||
97 | regs->psw.mask &= ~PSW_MASK_PER; | 66 | regs->psw.mask &= ~PSW_MASK_PER; |
98 | 67 | return; | |
99 | if (per_info->control_regs.bits.em_storage_alteration) | ||
100 | per_info->control_regs.bits.storage_alt_space_ctl = 1; | ||
101 | else | ||
102 | per_info->control_regs.bits.storage_alt_space_ctl = 0; | ||
103 | |||
104 | if (task == current) { | ||
105 | __ctl_store(cr_words, 9, 11); | ||
106 | if (memcmp(&cr_words, &per_info->control_regs.words, | ||
107 | sizeof(cr_words)) != 0) | ||
108 | __ctl_load(per_info->control_regs.words, 9, 11); | ||
109 | } | 68 | } |
69 | regs->psw.mask |= PSW_MASK_PER; | ||
70 | __ctl_store(old, 9, 11); | ||
71 | if (memcmp(new, &old, sizeof(struct per_regs)) != 0) | ||
72 | __ctl_load(*new, 9, 11); | ||
110 | } | 73 | } |
111 | 74 | ||
112 | void user_enable_single_step(struct task_struct *task) | 75 | void user_enable_single_step(struct task_struct *task) |
113 | { | 76 | { |
114 | task->thread.per_info.single_step = 1; | 77 | set_tsk_thread_flag(task, TIF_SINGLE_STEP); |
115 | FixPerRegisters(task); | 78 | if (task == current) |
79 | update_per_regs(task); | ||
116 | } | 80 | } |
117 | 81 | ||
118 | void user_disable_single_step(struct task_struct *task) | 82 | void user_disable_single_step(struct task_struct *task) |
119 | { | 83 | { |
120 | task->thread.per_info.single_step = 0; | 84 | clear_tsk_thread_flag(task, TIF_SINGLE_STEP); |
121 | FixPerRegisters(task); | 85 | if (task == current) |
86 | update_per_regs(task); | ||
122 | } | 87 | } |
123 | 88 | ||
124 | /* | 89 | /* |
125 | * Called by kernel/ptrace.c when detaching.. | 90 | * Called by kernel/ptrace.c when detaching.. |
126 | * | 91 | * |
127 | * Make sure single step bits etc are not set. | 92 | * Clear all debugging related fields. |
128 | */ | 93 | */ |
129 | void | 94 | void ptrace_disable(struct task_struct *task) |
130 | ptrace_disable(struct task_struct *child) | ||
131 | { | 95 | { |
132 | /* make sure the single step bit is not set. */ | 96 | memset(&task->thread.per_user, 0, sizeof(task->thread.per_user)); |
133 | user_disable_single_step(child); | 97 | memset(&task->thread.per_event, 0, sizeof(task->thread.per_event)); |
98 | clear_tsk_thread_flag(task, TIF_SINGLE_STEP); | ||
99 | clear_tsk_thread_flag(task, TIF_PER_TRAP); | ||
134 | } | 100 | } |
135 | 101 | ||
136 | #ifndef CONFIG_64BIT | 102 | #ifndef CONFIG_64BIT |
@@ -139,6 +105,47 @@ ptrace_disable(struct task_struct *child) | |||
139 | # define __ADDR_MASK 7 | 105 | # define __ADDR_MASK 7 |
140 | #endif | 106 | #endif |
141 | 107 | ||
108 | static inline unsigned long __peek_user_per(struct task_struct *child, | ||
109 | addr_t addr) | ||
110 | { | ||
111 | struct per_struct_kernel *dummy = NULL; | ||
112 | |||
113 | if (addr == (addr_t) &dummy->cr9) | ||
114 | /* Control bits of the active per set. */ | ||
115 | return test_thread_flag(TIF_SINGLE_STEP) ? | ||
116 | PER_EVENT_IFETCH : child->thread.per_user.control; | ||
117 | else if (addr == (addr_t) &dummy->cr10) | ||
118 | /* Start address of the active per set. */ | ||
119 | return test_thread_flag(TIF_SINGLE_STEP) ? | ||
120 | 0 : child->thread.per_user.start; | ||
121 | else if (addr == (addr_t) &dummy->cr11) | ||
122 | /* End address of the active per set. */ | ||
123 | return test_thread_flag(TIF_SINGLE_STEP) ? | ||
124 | PSW_ADDR_INSN : child->thread.per_user.end; | ||
125 | else if (addr == (addr_t) &dummy->bits) | ||
126 | /* Single-step bit. */ | ||
127 | return test_thread_flag(TIF_SINGLE_STEP) ? | ||
128 | (1UL << (BITS_PER_LONG - 1)) : 0; | ||
129 | else if (addr == (addr_t) &dummy->starting_addr) | ||
130 | /* Start address of the user specified per set. */ | ||
131 | return child->thread.per_user.start; | ||
132 | else if (addr == (addr_t) &dummy->ending_addr) | ||
133 | /* End address of the user specified per set. */ | ||
134 | return child->thread.per_user.end; | ||
135 | else if (addr == (addr_t) &dummy->perc_atmid) | ||
136 | /* PER code, ATMID and AI of the last PER trap */ | ||
137 | return (unsigned long) | ||
138 | child->thread.per_event.cause << (BITS_PER_LONG - 16); | ||
139 | else if (addr == (addr_t) &dummy->address) | ||
140 | /* Address of the last PER trap */ | ||
141 | return child->thread.per_event.address; | ||
142 | else if (addr == (addr_t) &dummy->access_id) | ||
143 | /* Access id of the last PER trap */ | ||
144 | return (unsigned long) | ||
145 | child->thread.per_event.paid << (BITS_PER_LONG - 8); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
142 | /* | 149 | /* |
143 | * Read the word at offset addr from the user area of a process. The | 150 | * Read the word at offset addr from the user area of a process. The |
144 | * trouble here is that the information is littered over different | 151 | * trouble here is that the information is littered over different |
@@ -204,10 +211,10 @@ static unsigned long __peek_user(struct task_struct *child, addr_t addr) | |||
204 | 211 | ||
205 | } else if (addr < (addr_t) (&dummy->regs.per_info + 1)) { | 212 | } else if (addr < (addr_t) (&dummy->regs.per_info + 1)) { |
206 | /* | 213 | /* |
207 | * per_info is found in the thread structure | 214 | * Handle access to the per_info structure. |
208 | */ | 215 | */ |
209 | offset = addr - (addr_t) &dummy->regs.per_info; | 216 | addr -= (addr_t) &dummy->regs.per_info; |
210 | tmp = *(addr_t *)((addr_t) &child->thread.per_info + offset); | 217 | tmp = __peek_user_per(child, addr); |
211 | 218 | ||
212 | } else | 219 | } else |
213 | tmp = 0; | 220 | tmp = 0; |
@@ -237,6 +244,35 @@ peek_user(struct task_struct *child, addr_t addr, addr_t data) | |||
237 | return put_user(tmp, (addr_t __user *) data); | 244 | return put_user(tmp, (addr_t __user *) data); |
238 | } | 245 | } |
239 | 246 | ||
247 | static inline void __poke_user_per(struct task_struct *child, | ||
248 | addr_t addr, addr_t data) | ||
249 | { | ||
250 | struct per_struct_kernel *dummy = NULL; | ||
251 | |||
252 | /* | ||
253 | * There are only three fields in the per_info struct that the | ||
254 | * debugger user can write to. | ||
255 | * 1) cr9: the debugger wants to set a new PER event mask | ||
256 | * 2) starting_addr: the debugger wants to set a new starting | ||
257 | * address to use with the PER event mask. | ||
258 | * 3) ending_addr: the debugger wants to set a new ending | ||
259 | * address to use with the PER event mask. | ||
260 | * The user specified PER event mask and the start and end | ||
261 | * addresses are used only if single stepping is not in effect. | ||
262 | * Writes to any other field in per_info are ignored. | ||
263 | */ | ||
264 | if (addr == (addr_t) &dummy->cr9) | ||
265 | /* PER event mask of the user specified per set. */ | ||
266 | child->thread.per_user.control = | ||
267 | data & (PER_EVENT_MASK | PER_CONTROL_MASK); | ||
268 | else if (addr == (addr_t) &dummy->starting_addr) | ||
269 | /* Starting address of the user specified per set. */ | ||
270 | child->thread.per_user.start = data; | ||
271 | else if (addr == (addr_t) &dummy->ending_addr) | ||
272 | /* Ending address of the user specified per set. */ | ||
273 | child->thread.per_user.end = data; | ||
274 | } | ||
275 | |||
240 | /* | 276 | /* |
241 | * Write a word to the user area of a process at location addr. This | 277 | * Write a word to the user area of a process at location addr. This |
242 | * operation does have an additional problem compared to peek_user. | 278 | * operation does have an additional problem compared to peek_user. |
@@ -311,19 +347,17 @@ static int __poke_user(struct task_struct *child, addr_t addr, addr_t data) | |||
311 | 347 | ||
312 | } else if (addr < (addr_t) (&dummy->regs.per_info + 1)) { | 348 | } else if (addr < (addr_t) (&dummy->regs.per_info + 1)) { |
313 | /* | 349 | /* |
314 | * per_info is found in the thread structure | 350 | * Handle access to the per_info structure. |
315 | */ | 351 | */ |
316 | offset = addr - (addr_t) &dummy->regs.per_info; | 352 | addr -= (addr_t) &dummy->regs.per_info; |
317 | *(addr_t *)((addr_t) &child->thread.per_info + offset) = data; | 353 | __poke_user_per(child, addr, data); |
318 | 354 | ||
319 | } | 355 | } |
320 | 356 | ||
321 | FixPerRegisters(child); | ||
322 | return 0; | 357 | return 0; |
323 | } | 358 | } |
324 | 359 | ||
325 | static int | 360 | static int poke_user(struct task_struct *child, addr_t addr, addr_t data) |
326 | poke_user(struct task_struct *child, addr_t addr, addr_t data) | ||
327 | { | 361 | { |
328 | addr_t mask; | 362 | addr_t mask; |
329 | 363 | ||
@@ -410,12 +444,53 @@ long arch_ptrace(struct task_struct *child, long request, | |||
410 | */ | 444 | */ |
411 | 445 | ||
412 | /* | 446 | /* |
447 | * Same as peek_user_per but for a 31 bit program. | ||
448 | */ | ||
449 | static inline __u32 __peek_user_per_compat(struct task_struct *child, | ||
450 | addr_t addr) | ||
451 | { | ||
452 | struct compat_per_struct_kernel *dummy32 = NULL; | ||
453 | |||
454 | if (addr == (addr_t) &dummy32->cr9) | ||
455 | /* Control bits of the active per set. */ | ||
456 | return (__u32) test_thread_flag(TIF_SINGLE_STEP) ? | ||
457 | PER_EVENT_IFETCH : child->thread.per_user.control; | ||
458 | else if (addr == (addr_t) &dummy32->cr10) | ||
459 | /* Start address of the active per set. */ | ||
460 | return (__u32) test_thread_flag(TIF_SINGLE_STEP) ? | ||
461 | 0 : child->thread.per_user.start; | ||
462 | else if (addr == (addr_t) &dummy32->cr11) | ||
463 | /* End address of the active per set. */ | ||
464 | return test_thread_flag(TIF_SINGLE_STEP) ? | ||
465 | PSW32_ADDR_INSN : child->thread.per_user.end; | ||
466 | else if (addr == (addr_t) &dummy32->bits) | ||
467 | /* Single-step bit. */ | ||
468 | return (__u32) test_thread_flag(TIF_SINGLE_STEP) ? | ||
469 | 0x80000000 : 0; | ||
470 | else if (addr == (addr_t) &dummy32->starting_addr) | ||
471 | /* Start address of the user specified per set. */ | ||
472 | return (__u32) child->thread.per_user.start; | ||
473 | else if (addr == (addr_t) &dummy32->ending_addr) | ||
474 | /* End address of the user specified per set. */ | ||
475 | return (__u32) child->thread.per_user.end; | ||
476 | else if (addr == (addr_t) &dummy32->perc_atmid) | ||
477 | /* PER code, ATMID and AI of the last PER trap */ | ||
478 | return (__u32) child->thread.per_event.cause << 16; | ||
479 | else if (addr == (addr_t) &dummy32->address) | ||
480 | /* Address of the last PER trap */ | ||
481 | return (__u32) child->thread.per_event.address; | ||
482 | else if (addr == (addr_t) &dummy32->access_id) | ||
483 | /* Access id of the last PER trap */ | ||
484 | return (__u32) child->thread.per_event.paid << 24; | ||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | /* | ||
413 | * Same as peek_user but for a 31 bit program. | 489 | * Same as peek_user but for a 31 bit program. |
414 | */ | 490 | */ |
415 | static u32 __peek_user_compat(struct task_struct *child, addr_t addr) | 491 | static u32 __peek_user_compat(struct task_struct *child, addr_t addr) |
416 | { | 492 | { |
417 | struct user32 *dummy32 = NULL; | 493 | struct compat_user *dummy32 = NULL; |
418 | per_struct32 *dummy_per32 = NULL; | ||
419 | addr_t offset; | 494 | addr_t offset; |
420 | __u32 tmp; | 495 | __u32 tmp; |
421 | 496 | ||
@@ -465,19 +540,10 @@ static u32 __peek_user_compat(struct task_struct *child, addr_t addr) | |||
465 | 540 | ||
466 | } else if (addr < (addr_t) (&dummy32->regs.per_info + 1)) { | 541 | } else if (addr < (addr_t) (&dummy32->regs.per_info + 1)) { |
467 | /* | 542 | /* |
468 | * per_info is found in the thread structure | 543 | * Handle access to the per_info structure. |
469 | */ | 544 | */ |
470 | offset = addr - (addr_t) &dummy32->regs.per_info; | 545 | addr -= (addr_t) &dummy32->regs.per_info; |
471 | /* This is magic. See per_struct and per_struct32. */ | 546 | tmp = __peek_user_per_compat(child, addr); |
472 | if ((offset >= (addr_t) &dummy_per32->control_regs && | ||
473 | offset < (addr_t) (&dummy_per32->control_regs + 1)) || | ||
474 | (offset >= (addr_t) &dummy_per32->starting_addr && | ||
475 | offset <= (addr_t) &dummy_per32->ending_addr) || | ||
476 | offset == (addr_t) &dummy_per32->lowcore.words.address) | ||
477 | offset = offset*2 + 4; | ||
478 | else | ||
479 | offset = offset*2; | ||
480 | tmp = *(__u32 *)((addr_t) &child->thread.per_info + offset); | ||
481 | 547 | ||
482 | } else | 548 | } else |
483 | tmp = 0; | 549 | tmp = 0; |
@@ -498,13 +564,32 @@ static int peek_user_compat(struct task_struct *child, | |||
498 | } | 564 | } |
499 | 565 | ||
500 | /* | 566 | /* |
567 | * Same as poke_user_per but for a 31 bit program. | ||
568 | */ | ||
569 | static inline void __poke_user_per_compat(struct task_struct *child, | ||
570 | addr_t addr, __u32 data) | ||
571 | { | ||
572 | struct compat_per_struct_kernel *dummy32 = NULL; | ||
573 | |||
574 | if (addr == (addr_t) &dummy32->cr9) | ||
575 | /* PER event mask of the user specified per set. */ | ||
576 | child->thread.per_user.control = | ||
577 | data & (PER_EVENT_MASK | PER_CONTROL_MASK); | ||
578 | else if (addr == (addr_t) &dummy32->starting_addr) | ||
579 | /* Starting address of the user specified per set. */ | ||
580 | child->thread.per_user.start = data; | ||
581 | else if (addr == (addr_t) &dummy32->ending_addr) | ||
582 | /* Ending address of the user specified per set. */ | ||
583 | child->thread.per_user.end = data; | ||
584 | } | ||
585 | |||
586 | /* | ||
501 | * Same as poke_user but for a 31 bit program. | 587 | * Same as poke_user but for a 31 bit program. |
502 | */ | 588 | */ |
503 | static int __poke_user_compat(struct task_struct *child, | 589 | static int __poke_user_compat(struct task_struct *child, |
504 | addr_t addr, addr_t data) | 590 | addr_t addr, addr_t data) |
505 | { | 591 | { |
506 | struct user32 *dummy32 = NULL; | 592 | struct compat_user *dummy32 = NULL; |
507 | per_struct32 *dummy_per32 = NULL; | ||
508 | __u32 tmp = (__u32) data; | 593 | __u32 tmp = (__u32) data; |
509 | addr_t offset; | 594 | addr_t offset; |
510 | 595 | ||
@@ -561,37 +646,20 @@ static int __poke_user_compat(struct task_struct *child, | |||
561 | 646 | ||
562 | } else if (addr < (addr_t) (&dummy32->regs.per_info + 1)) { | 647 | } else if (addr < (addr_t) (&dummy32->regs.per_info + 1)) { |
563 | /* | 648 | /* |
564 | * per_info is found in the thread structure. | 649 | * Handle access to the per_info structure. |
565 | */ | ||
566 | offset = addr - (addr_t) &dummy32->regs.per_info; | ||
567 | /* | ||
568 | * This is magic. See per_struct and per_struct32. | ||
569 | * By incident the offsets in per_struct are exactly | ||
570 | * twice the offsets in per_struct32 for all fields. | ||
571 | * The 8 byte fields need special handling though, | ||
572 | * because the second half (bytes 4-7) is needed and | ||
573 | * not the first half. | ||
574 | */ | 650 | */ |
575 | if ((offset >= (addr_t) &dummy_per32->control_regs && | 651 | addr -= (addr_t) &dummy32->regs.per_info; |
576 | offset < (addr_t) (&dummy_per32->control_regs + 1)) || | 652 | __poke_user_per_compat(child, addr, data); |
577 | (offset >= (addr_t) &dummy_per32->starting_addr && | ||
578 | offset <= (addr_t) &dummy_per32->ending_addr) || | ||
579 | offset == (addr_t) &dummy_per32->lowcore.words.address) | ||
580 | offset = offset*2 + 4; | ||
581 | else | ||
582 | offset = offset*2; | ||
583 | *(__u32 *)((addr_t) &child->thread.per_info + offset) = tmp; | ||
584 | |||
585 | } | 653 | } |
586 | 654 | ||
587 | FixPerRegisters(child); | ||
588 | return 0; | 655 | return 0; |
589 | } | 656 | } |
590 | 657 | ||
591 | static int poke_user_compat(struct task_struct *child, | 658 | static int poke_user_compat(struct task_struct *child, |
592 | addr_t addr, addr_t data) | 659 | addr_t addr, addr_t data) |
593 | { | 660 | { |
594 | if (!is_compat_task() || (addr & 3) || addr > sizeof(struct user32) - 3) | 661 | if (!is_compat_task() || (addr & 3) || |
662 | addr > sizeof(struct compat_user) - 3) | ||
595 | return -EIO; | 663 | return -EIO; |
596 | 664 | ||
597 | return __poke_user_compat(child, addr, data); | 665 | return __poke_user_compat(child, addr, data); |
@@ -602,7 +670,7 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request, | |||
602 | { | 670 | { |
603 | unsigned long addr = caddr; | 671 | unsigned long addr = caddr; |
604 | unsigned long data = cdata; | 672 | unsigned long data = cdata; |
605 | ptrace_area_emu31 parea; | 673 | compat_ptrace_area parea; |
606 | int copied, ret; | 674 | int copied, ret; |
607 | 675 | ||
608 | switch (request) { | 676 | switch (request) { |