diff options
author | Nicholas Bellinger <nab@linux-iscsi.org> | 2016-01-12 00:31:09 -0500 |
---|---|---|
committer | Nicholas Bellinger <nab@linux-iscsi.org> | 2016-02-03 17:09:07 -0500 |
commit | febe562c20dfa8f33bee7d419c6b517986a5aa33 (patch) | |
tree | 8fab8d2713f877b4464739b302bda4b21c12436b /drivers/target | |
parent | a07100e00ac42a4474530ce17b4978c9e06bde55 (diff) |
target: Fix LUN_RESET active I/O handling for ACK_KREF
This patch fixes a NULL pointer se_cmd->cmd_kref < 0
refcount bug during TMR LUN_RESET with active se_cmd
I/O, that can be triggered during se_cmd descriptor
shutdown + release via core_tmr_drain_state_list() code.
To address this bug, add common __target_check_io_state()
helper for ABORT_TASK + LUN_RESET w/ CMD_T_COMPLETE
checking, and set CMD_T_ABORTED + obtain ->cmd_kref for
both cases ahead of last target_put_sess_cmd() after
TFO->aborted_task() -> transport_cmd_finish_abort()
callback has completed.
It also introduces SCF_ACK_KREF to determine when
transport_cmd_finish_abort() needs to drop the second
extra reference, ahead of calling target_put_sess_cmd()
for the final kref_put(&se_cmd->cmd_kref).
It also updates transport_cmd_check_stop() to avoid
holding se_cmd->t_state_lock while dropping se_cmd
device state via target_remove_from_state_list(), now
that core_tmr_drain_state_list() is holding the
se_device lock while checking se_cmd state from
within TMR logic.
Finally, move transport_put_cmd() release of SGL +
TMR + extended CDB memory into target_free_cmd_mem()
in order to avoid potential resource leaks in TMR
ABORT_TASK + LUN_RESET code-paths. Also update
target_release_cmd_kref() accordingly.
Reviewed-by: Quinn Tran <quinn.tran@qlogic.com>
Cc: Himanshu Madhani <himanshu.madhani@qlogic.com>
Cc: Sagi Grimberg <sagig@mellanox.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Hannes Reinecke <hare@suse.de>
Cc: Andy Grover <agrover@redhat.com>
Cc: Mike Christie <mchristi@redhat.com>
Cc: stable@vger.kernel.org # 3.10+
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
Diffstat (limited to 'drivers/target')
-rw-r--r-- | drivers/target/target_core_tmr.c | 69 | ||||
-rw-r--r-- | drivers/target/target_core_transport.c | 67 |
2 files changed, 75 insertions, 61 deletions
diff --git a/drivers/target/target_core_tmr.c b/drivers/target/target_core_tmr.c index fcdcb117c60d..fb3decc28589 100644 --- a/drivers/target/target_core_tmr.c +++ b/drivers/target/target_core_tmr.c | |||
@@ -107,6 +107,34 @@ static int target_check_cdb_and_preempt(struct list_head *list, | |||
107 | return 1; | 107 | return 1; |
108 | } | 108 | } |
109 | 109 | ||
110 | static bool __target_check_io_state(struct se_cmd *se_cmd) | ||
111 | { | ||
112 | struct se_session *sess = se_cmd->se_sess; | ||
113 | |||
114 | assert_spin_locked(&sess->sess_cmd_lock); | ||
115 | WARN_ON_ONCE(!irqs_disabled()); | ||
116 | /* | ||
117 | * If command already reached CMD_T_COMPLETE state within | ||
118 | * target_complete_cmd(), this se_cmd has been passed to | ||
119 | * fabric driver and will not be aborted. | ||
120 | * | ||
121 | * Otherwise, obtain a local se_cmd->cmd_kref now for TMR | ||
122 | * ABORT_TASK + LUN_RESET for CMD_T_ABORTED processing as | ||
123 | * long as se_cmd->cmd_kref is still active unless zero. | ||
124 | */ | ||
125 | spin_lock(&se_cmd->t_state_lock); | ||
126 | if (se_cmd->transport_state & CMD_T_COMPLETE) { | ||
127 | pr_debug("Attempted to abort io tag: %llu already complete," | ||
128 | " skipping\n", se_cmd->tag); | ||
129 | spin_unlock(&se_cmd->t_state_lock); | ||
130 | return false; | ||
131 | } | ||
132 | se_cmd->transport_state |= CMD_T_ABORTED; | ||
133 | spin_unlock(&se_cmd->t_state_lock); | ||
134 | |||
135 | return kref_get_unless_zero(&se_cmd->cmd_kref); | ||
136 | } | ||
137 | |||
110 | void core_tmr_abort_task( | 138 | void core_tmr_abort_task( |
111 | struct se_device *dev, | 139 | struct se_device *dev, |
112 | struct se_tmr_req *tmr, | 140 | struct se_tmr_req *tmr, |
@@ -130,34 +158,22 @@ void core_tmr_abort_task( | |||
130 | if (tmr->ref_task_tag != ref_tag) | 158 | if (tmr->ref_task_tag != ref_tag) |
131 | continue; | 159 | continue; |
132 | 160 | ||
133 | if (!kref_get_unless_zero(&se_cmd->cmd_kref)) | ||
134 | continue; | ||
135 | |||
136 | printk("ABORT_TASK: Found referenced %s task_tag: %llu\n", | 161 | printk("ABORT_TASK: Found referenced %s task_tag: %llu\n", |
137 | se_cmd->se_tfo->get_fabric_name(), ref_tag); | 162 | se_cmd->se_tfo->get_fabric_name(), ref_tag); |
138 | 163 | ||
139 | spin_lock(&se_cmd->t_state_lock); | 164 | if (!__target_check_io_state(se_cmd)) { |
140 | if (se_cmd->transport_state & CMD_T_COMPLETE) { | ||
141 | printk("ABORT_TASK: ref_tag: %llu already complete," | ||
142 | " skipping\n", ref_tag); | ||
143 | spin_unlock(&se_cmd->t_state_lock); | ||
144 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); | 165 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); |
145 | |||
146 | target_put_sess_cmd(se_cmd); | 166 | target_put_sess_cmd(se_cmd); |
147 | |||
148 | goto out; | 167 | goto out; |
149 | } | 168 | } |
150 | se_cmd->transport_state |= CMD_T_ABORTED; | ||
151 | spin_unlock(&se_cmd->t_state_lock); | ||
152 | |||
153 | list_del_init(&se_cmd->se_cmd_list); | 169 | list_del_init(&se_cmd->se_cmd_list); |
154 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); | 170 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); |
155 | 171 | ||
156 | cancel_work_sync(&se_cmd->work); | 172 | cancel_work_sync(&se_cmd->work); |
157 | transport_wait_for_tasks(se_cmd); | 173 | transport_wait_for_tasks(se_cmd); |
158 | 174 | ||
159 | target_put_sess_cmd(se_cmd); | ||
160 | transport_cmd_finish_abort(se_cmd, true); | 175 | transport_cmd_finish_abort(se_cmd, true); |
176 | target_put_sess_cmd(se_cmd); | ||
161 | 177 | ||
162 | printk("ABORT_TASK: Sending TMR_FUNCTION_COMPLETE for" | 178 | printk("ABORT_TASK: Sending TMR_FUNCTION_COMPLETE for" |
163 | " ref_tag: %llu\n", ref_tag); | 179 | " ref_tag: %llu\n", ref_tag); |
@@ -242,8 +258,10 @@ static void core_tmr_drain_state_list( | |||
242 | struct list_head *preempt_and_abort_list) | 258 | struct list_head *preempt_and_abort_list) |
243 | { | 259 | { |
244 | LIST_HEAD(drain_task_list); | 260 | LIST_HEAD(drain_task_list); |
261 | struct se_session *sess; | ||
245 | struct se_cmd *cmd, *next; | 262 | struct se_cmd *cmd, *next; |
246 | unsigned long flags; | 263 | unsigned long flags; |
264 | int rc; | ||
247 | 265 | ||
248 | /* | 266 | /* |
249 | * Complete outstanding commands with TASK_ABORTED SAM status. | 267 | * Complete outstanding commands with TASK_ABORTED SAM status. |
@@ -282,6 +300,16 @@ static void core_tmr_drain_state_list( | |||
282 | if (prout_cmd == cmd) | 300 | if (prout_cmd == cmd) |
283 | continue; | 301 | continue; |
284 | 302 | ||
303 | sess = cmd->se_sess; | ||
304 | if (WARN_ON_ONCE(!sess)) | ||
305 | continue; | ||
306 | |||
307 | spin_lock(&sess->sess_cmd_lock); | ||
308 | rc = __target_check_io_state(cmd); | ||
309 | spin_unlock(&sess->sess_cmd_lock); | ||
310 | if (!rc) | ||
311 | continue; | ||
312 | |||
285 | list_move_tail(&cmd->state_list, &drain_task_list); | 313 | list_move_tail(&cmd->state_list, &drain_task_list); |
286 | cmd->state_active = false; | 314 | cmd->state_active = false; |
287 | } | 315 | } |
@@ -289,7 +317,7 @@ static void core_tmr_drain_state_list( | |||
289 | 317 | ||
290 | while (!list_empty(&drain_task_list)) { | 318 | while (!list_empty(&drain_task_list)) { |
291 | cmd = list_entry(drain_task_list.next, struct se_cmd, state_list); | 319 | cmd = list_entry(drain_task_list.next, struct se_cmd, state_list); |
292 | list_del(&cmd->state_list); | 320 | list_del_init(&cmd->state_list); |
293 | 321 | ||
294 | pr_debug("LUN_RESET: %s cmd: %p" | 322 | pr_debug("LUN_RESET: %s cmd: %p" |
295 | " ITT/CmdSN: 0x%08llx/0x%08x, i_state: %d, t_state: %d" | 323 | " ITT/CmdSN: 0x%08llx/0x%08x, i_state: %d, t_state: %d" |
@@ -313,16 +341,11 @@ static void core_tmr_drain_state_list( | |||
313 | * loop above, but we do it down here given that | 341 | * loop above, but we do it down here given that |
314 | * cancel_work_sync may block. | 342 | * cancel_work_sync may block. |
315 | */ | 343 | */ |
316 | if (cmd->t_state == TRANSPORT_COMPLETE) | 344 | cancel_work_sync(&cmd->work); |
317 | cancel_work_sync(&cmd->work); | 345 | transport_wait_for_tasks(cmd); |
318 | |||
319 | spin_lock_irqsave(&cmd->t_state_lock, flags); | ||
320 | target_stop_cmd(cmd, &flags); | ||
321 | |||
322 | cmd->transport_state |= CMD_T_ABORTED; | ||
323 | spin_unlock_irqrestore(&cmd->t_state_lock, flags); | ||
324 | 346 | ||
325 | core_tmr_handle_tas_abort(tmr_nacl, cmd, tas); | 347 | core_tmr_handle_tas_abort(tmr_nacl, cmd, tas); |
348 | target_put_sess_cmd(cmd); | ||
326 | } | 349 | } |
327 | } | 350 | } |
328 | 351 | ||
diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 9f3608e10f25..af52f8bd8954 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c | |||
@@ -534,9 +534,6 @@ void transport_deregister_session(struct se_session *se_sess) | |||
534 | } | 534 | } |
535 | EXPORT_SYMBOL(transport_deregister_session); | 535 | EXPORT_SYMBOL(transport_deregister_session); |
536 | 536 | ||
537 | /* | ||
538 | * Called with cmd->t_state_lock held. | ||
539 | */ | ||
540 | static void target_remove_from_state_list(struct se_cmd *cmd) | 537 | static void target_remove_from_state_list(struct se_cmd *cmd) |
541 | { | 538 | { |
542 | struct se_device *dev = cmd->se_dev; | 539 | struct se_device *dev = cmd->se_dev; |
@@ -561,10 +558,6 @@ static int transport_cmd_check_stop(struct se_cmd *cmd, bool remove_from_lists, | |||
561 | { | 558 | { |
562 | unsigned long flags; | 559 | unsigned long flags; |
563 | 560 | ||
564 | spin_lock_irqsave(&cmd->t_state_lock, flags); | ||
565 | if (write_pending) | ||
566 | cmd->t_state = TRANSPORT_WRITE_PENDING; | ||
567 | |||
568 | if (remove_from_lists) { | 561 | if (remove_from_lists) { |
569 | target_remove_from_state_list(cmd); | 562 | target_remove_from_state_list(cmd); |
570 | 563 | ||
@@ -574,6 +567,10 @@ static int transport_cmd_check_stop(struct se_cmd *cmd, bool remove_from_lists, | |||
574 | cmd->se_lun = NULL; | 567 | cmd->se_lun = NULL; |
575 | } | 568 | } |
576 | 569 | ||
570 | spin_lock_irqsave(&cmd->t_state_lock, flags); | ||
571 | if (write_pending) | ||
572 | cmd->t_state = TRANSPORT_WRITE_PENDING; | ||
573 | |||
577 | /* | 574 | /* |
578 | * Determine if frontend context caller is requesting the stopping of | 575 | * Determine if frontend context caller is requesting the stopping of |
579 | * this command for frontend exceptions. | 576 | * this command for frontend exceptions. |
@@ -627,6 +624,8 @@ static void transport_lun_remove_cmd(struct se_cmd *cmd) | |||
627 | 624 | ||
628 | void transport_cmd_finish_abort(struct se_cmd *cmd, int remove) | 625 | void transport_cmd_finish_abort(struct se_cmd *cmd, int remove) |
629 | { | 626 | { |
627 | bool ack_kref = (cmd->se_cmd_flags & SCF_ACK_KREF); | ||
628 | |||
630 | if (cmd->se_cmd_flags & SCF_SE_LUN_CMD) | 629 | if (cmd->se_cmd_flags & SCF_SE_LUN_CMD) |
631 | transport_lun_remove_cmd(cmd); | 630 | transport_lun_remove_cmd(cmd); |
632 | /* | 631 | /* |
@@ -638,7 +637,7 @@ void transport_cmd_finish_abort(struct se_cmd *cmd, int remove) | |||
638 | 637 | ||
639 | if (transport_cmd_check_stop_to_fabric(cmd)) | 638 | if (transport_cmd_check_stop_to_fabric(cmd)) |
640 | return; | 639 | return; |
641 | if (remove) | 640 | if (remove && ack_kref) |
642 | transport_put_cmd(cmd); | 641 | transport_put_cmd(cmd); |
643 | } | 642 | } |
644 | 643 | ||
@@ -706,7 +705,7 @@ void target_complete_cmd(struct se_cmd *cmd, u8 scsi_status) | |||
706 | * Check for case where an explicit ABORT_TASK has been received | 705 | * Check for case where an explicit ABORT_TASK has been received |
707 | * and transport_wait_for_tasks() will be waiting for completion.. | 706 | * and transport_wait_for_tasks() will be waiting for completion.. |
708 | */ | 707 | */ |
709 | if (cmd->transport_state & CMD_T_ABORTED && | 708 | if (cmd->transport_state & CMD_T_ABORTED || |
710 | cmd->transport_state & CMD_T_STOP) { | 709 | cmd->transport_state & CMD_T_STOP) { |
711 | spin_unlock_irqrestore(&cmd->t_state_lock, flags); | 710 | spin_unlock_irqrestore(&cmd->t_state_lock, flags); |
712 | complete_all(&cmd->t_transport_stop_comp); | 711 | complete_all(&cmd->t_transport_stop_comp); |
@@ -2222,20 +2221,14 @@ static inline void transport_free_pages(struct se_cmd *cmd) | |||
2222 | } | 2221 | } |
2223 | 2222 | ||
2224 | /** | 2223 | /** |
2225 | * transport_release_cmd - free a command | 2224 | * transport_put_cmd - release a reference to a command |
2226 | * @cmd: command to free | 2225 | * @cmd: command to release |
2227 | * | 2226 | * |
2228 | * This routine unconditionally frees a command, and reference counting | 2227 | * This routine releases our reference to the command and frees it if possible. |
2229 | * or list removal must be done in the caller. | ||
2230 | */ | 2228 | */ |
2231 | static int transport_release_cmd(struct se_cmd *cmd) | 2229 | static int transport_put_cmd(struct se_cmd *cmd) |
2232 | { | 2230 | { |
2233 | BUG_ON(!cmd->se_tfo); | 2231 | BUG_ON(!cmd->se_tfo); |
2234 | |||
2235 | if (cmd->se_cmd_flags & SCF_SCSI_TMR_CDB) | ||
2236 | core_tmr_release_req(cmd->se_tmr_req); | ||
2237 | if (cmd->t_task_cdb != cmd->__t_task_cdb) | ||
2238 | kfree(cmd->t_task_cdb); | ||
2239 | /* | 2232 | /* |
2240 | * If this cmd has been setup with target_get_sess_cmd(), drop | 2233 | * If this cmd has been setup with target_get_sess_cmd(), drop |
2241 | * the kref and call ->release_cmd() in kref callback. | 2234 | * the kref and call ->release_cmd() in kref callback. |
@@ -2243,18 +2236,6 @@ static int transport_release_cmd(struct se_cmd *cmd) | |||
2243 | return target_put_sess_cmd(cmd); | 2236 | return target_put_sess_cmd(cmd); |
2244 | } | 2237 | } |
2245 | 2238 | ||
2246 | /** | ||
2247 | * transport_put_cmd - release a reference to a command | ||
2248 | * @cmd: command to release | ||
2249 | * | ||
2250 | * This routine releases our reference to the command and frees it if possible. | ||
2251 | */ | ||
2252 | static int transport_put_cmd(struct se_cmd *cmd) | ||
2253 | { | ||
2254 | transport_free_pages(cmd); | ||
2255 | return transport_release_cmd(cmd); | ||
2256 | } | ||
2257 | |||
2258 | void *transport_kmap_data_sg(struct se_cmd *cmd) | 2239 | void *transport_kmap_data_sg(struct se_cmd *cmd) |
2259 | { | 2240 | { |
2260 | struct scatterlist *sg = cmd->t_data_sg; | 2241 | struct scatterlist *sg = cmd->t_data_sg; |
@@ -2452,14 +2433,13 @@ static void transport_write_pending_qf(struct se_cmd *cmd) | |||
2452 | 2433 | ||
2453 | int transport_generic_free_cmd(struct se_cmd *cmd, int wait_for_tasks) | 2434 | int transport_generic_free_cmd(struct se_cmd *cmd, int wait_for_tasks) |
2454 | { | 2435 | { |
2455 | unsigned long flags; | ||
2456 | int ret = 0; | 2436 | int ret = 0; |
2457 | 2437 | ||
2458 | if (!(cmd->se_cmd_flags & SCF_SE_LUN_CMD)) { | 2438 | if (!(cmd->se_cmd_flags & SCF_SE_LUN_CMD)) { |
2459 | if (wait_for_tasks && (cmd->se_cmd_flags & SCF_SCSI_TMR_CDB)) | 2439 | if (wait_for_tasks && (cmd->se_cmd_flags & SCF_SCSI_TMR_CDB)) |
2460 | transport_wait_for_tasks(cmd); | 2440 | transport_wait_for_tasks(cmd); |
2461 | 2441 | ||
2462 | ret = transport_release_cmd(cmd); | 2442 | ret = transport_put_cmd(cmd); |
2463 | } else { | 2443 | } else { |
2464 | if (wait_for_tasks) | 2444 | if (wait_for_tasks) |
2465 | transport_wait_for_tasks(cmd); | 2445 | transport_wait_for_tasks(cmd); |
@@ -2468,11 +2448,8 @@ int transport_generic_free_cmd(struct se_cmd *cmd, int wait_for_tasks) | |||
2468 | * has already added se_cmd to state_list, but fabric has | 2448 | * has already added se_cmd to state_list, but fabric has |
2469 | * failed command before I/O submission. | 2449 | * failed command before I/O submission. |
2470 | */ | 2450 | */ |
2471 | if (cmd->state_active) { | 2451 | if (cmd->state_active) |
2472 | spin_lock_irqsave(&cmd->t_state_lock, flags); | ||
2473 | target_remove_from_state_list(cmd); | 2452 | target_remove_from_state_list(cmd); |
2474 | spin_unlock_irqrestore(&cmd->t_state_lock, flags); | ||
2475 | } | ||
2476 | 2453 | ||
2477 | if (cmd->se_lun) | 2454 | if (cmd->se_lun) |
2478 | transport_lun_remove_cmd(cmd); | 2455 | transport_lun_remove_cmd(cmd); |
@@ -2517,6 +2494,16 @@ out: | |||
2517 | } | 2494 | } |
2518 | EXPORT_SYMBOL(target_get_sess_cmd); | 2495 | EXPORT_SYMBOL(target_get_sess_cmd); |
2519 | 2496 | ||
2497 | static void target_free_cmd_mem(struct se_cmd *cmd) | ||
2498 | { | ||
2499 | transport_free_pages(cmd); | ||
2500 | |||
2501 | if (cmd->se_cmd_flags & SCF_SCSI_TMR_CDB) | ||
2502 | core_tmr_release_req(cmd->se_tmr_req); | ||
2503 | if (cmd->t_task_cdb != cmd->__t_task_cdb) | ||
2504 | kfree(cmd->t_task_cdb); | ||
2505 | } | ||
2506 | |||
2520 | static void target_release_cmd_kref(struct kref *kref) | 2507 | static void target_release_cmd_kref(struct kref *kref) |
2521 | { | 2508 | { |
2522 | struct se_cmd *se_cmd = container_of(kref, struct se_cmd, cmd_kref); | 2509 | struct se_cmd *se_cmd = container_of(kref, struct se_cmd, cmd_kref); |
@@ -2526,17 +2513,20 @@ static void target_release_cmd_kref(struct kref *kref) | |||
2526 | spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); | 2513 | spin_lock_irqsave(&se_sess->sess_cmd_lock, flags); |
2527 | if (list_empty(&se_cmd->se_cmd_list)) { | 2514 | if (list_empty(&se_cmd->se_cmd_list)) { |
2528 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); | 2515 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); |
2516 | target_free_cmd_mem(se_cmd); | ||
2529 | se_cmd->se_tfo->release_cmd(se_cmd); | 2517 | se_cmd->se_tfo->release_cmd(se_cmd); |
2530 | return; | 2518 | return; |
2531 | } | 2519 | } |
2532 | if (se_sess->sess_tearing_down && se_cmd->cmd_wait_set) { | 2520 | if (se_sess->sess_tearing_down && se_cmd->cmd_wait_set) { |
2533 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); | 2521 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); |
2522 | target_free_cmd_mem(se_cmd); | ||
2534 | complete(&se_cmd->cmd_wait_comp); | 2523 | complete(&se_cmd->cmd_wait_comp); |
2535 | return; | 2524 | return; |
2536 | } | 2525 | } |
2537 | list_del(&se_cmd->se_cmd_list); | 2526 | list_del(&se_cmd->se_cmd_list); |
2538 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); | 2527 | spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); |
2539 | 2528 | ||
2529 | target_free_cmd_mem(se_cmd); | ||
2540 | se_cmd->se_tfo->release_cmd(se_cmd); | 2530 | se_cmd->se_tfo->release_cmd(se_cmd); |
2541 | } | 2531 | } |
2542 | 2532 | ||
@@ -2548,6 +2538,7 @@ int target_put_sess_cmd(struct se_cmd *se_cmd) | |||
2548 | struct se_session *se_sess = se_cmd->se_sess; | 2538 | struct se_session *se_sess = se_cmd->se_sess; |
2549 | 2539 | ||
2550 | if (!se_sess) { | 2540 | if (!se_sess) { |
2541 | target_free_cmd_mem(se_cmd); | ||
2551 | se_cmd->se_tfo->release_cmd(se_cmd); | 2542 | se_cmd->se_tfo->release_cmd(se_cmd); |
2552 | return 1; | 2543 | return 1; |
2553 | } | 2544 | } |