1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * Copyright 2022 Oxide Computer Company 26 */ 27 28 29 #include <sys/types.h> 30 #include <sys/systm.h> 31 #include <sys/stat.h> 32 #include <sys/errno.h> 33 #include <sys/kmem.h> 34 #include <sys/sysmacros.h> 35 #include <sys/debug.h> 36 #include <sys/poll_impl.h> 37 #include <sys/port_impl.h> 38 39 #define PORTHASH_START 256 /* start cache space for events */ 40 #define PORTHASH_MULT 2 /* growth threshold and factor */ 41 42 /* local functions */ 43 static int port_fd_callback(void *, int *, pid_t, int, void *); 44 static int port_bind_pollhead(pollhead_t **, polldat_t *, short *); 45 static void port_close_sourcefd(void *, int, pid_t, int); 46 static void port_cache_insert_fd(port_fdcache_t *, polldat_t *); 47 48 /* 49 * port_fd_callback() 50 * The event port framework uses callback functions to notify associated 51 * event sources about actions on source specific objects. 52 * The source itself defines the "arg" required to identify the object with 53 * events. In the port_fd_callback() case the "arg" is a pointer to portfd_t 54 * structure. The portfd_t structure is specific for PORT_SOURCE_FD source. 55 * The port_fd_callback() function is notified in three cases: 56 * - PORT_CALLBACK_DEFAULT 57 * The object (fd) will be delivered to the application. 58 * - PORT_CALLBACK_DISSOCIATE 59 * The object (fd) will be dissociated from the port. 60 * - PORT_CALLBACK_CLOSE 61 * The object (fd) will be dissociated from the port because the port 62 * is being closed. 63 * A fd is shareable between processes only when 64 * - processes have the same fd id and 65 * - processes have the same fp. 66 * A fd becomes shareable: 67 * - on fork() across parent and child process and 68 * - when I_SENDFD is used to pass file descriptors between parent and child 69 * immediately after fork() (the sender and receiver must get the same 70 * file descriptor id). 71 * If a fd is shared between processes, all involved processes will get 72 * the same rights related to re-association of the fd with the port and 73 * retrieve of events from that fd. 74 * The process which associated the fd with a port for the first time 75 * becomes also the owner of the association. Only the owner of the 76 * association is allowed to dissociate the fd from the port. 77 */ 78 /* ARGSUSED */ 79 static int 80 port_fd_callback(void *arg, int *events, pid_t pid, int flag, void *evp) 81 { 82 portfd_t *pfd = (portfd_t *)arg; 83 polldat_t *pdp = PFTOD(pfd); 84 port_fdcache_t *pcp; 85 file_t *fp; 86 int error; 87 88 ASSERT((pdp != NULL) && (events != NULL)); 89 switch (flag) { 90 case PORT_CALLBACK_DEFAULT: 91 if (curproc->p_pid != pid) { 92 /* 93 * Check if current process is allowed to retrieve 94 * events from this fd. 95 */ 96 fp = getf(pdp->pd_fd); 97 if (fp == NULL) { 98 error = EACCES; /* deny delivery of events */ 99 break; 100 } 101 releasef(pdp->pd_fd); 102 if (fp != pdp->pd_fp) { 103 error = EACCES; /* deny delivery of events */ 104 break; 105 } 106 } 107 *events = pdp->pd_portev->portkev_events; /* update events */ 108 error = 0; 109 break; 110 case PORT_CALLBACK_DISSOCIATE: 111 error = 0; 112 break; 113 case PORT_CALLBACK_CLOSE: 114 /* remove polldat/portfd struct */ 115 pdp->pd_portev = NULL; 116 pcp = (port_fdcache_t *)pdp->pd_pcache; 117 mutex_enter(&pcp->pc_lock); 118 pdp->pd_fp = NULL; 119 pdp->pd_events = 0; 120 polldat_disassociate(pdp); 121 port_pcache_remove_fd(pcp, pfd); 122 mutex_exit(&pcp->pc_lock); 123 error = 0; 124 break; 125 default: 126 error = EINVAL; 127 break; 128 } 129 return (error); 130 } 131 132 /* 133 * This routine returns a pointer to a cached poll fd entry, or NULL if it 134 * does not find it in the hash table. 135 * The fd is used as index. 136 * The fd and the fp are used to detect a valid entry. 137 * This function returns a pointer to a valid portfd_t structure only when 138 * the fd and the fp in the args match the entries in polldat_t. 139 */ 140 portfd_t * 141 port_cache_lookup_fp(port_fdcache_t *pcp, int fd, file_t *fp) 142 { 143 polldat_t *pdp; 144 portfd_t **bucket; 145 146 ASSERT(MUTEX_HELD(&pcp->pc_lock)); 147 bucket = PORT_FD_BUCKET(pcp, fd); 148 pdp = PFTOD(*bucket); 149 while (pdp != NULL) { 150 if (pdp->pd_fd == fd && pdp->pd_fp == fp) 151 break; 152 pdp = pdp->pd_hashnext; 153 } 154 return (PDTOF(pdp)); 155 } 156 157 /* 158 * port_associate_fd() 159 * This function associates new file descriptors with a port or 160 * reactivate already associated file descriptors. 161 * The reactivation also updates the events types to be checked and the 162 * attached user pointer. 163 * Per port a cache is used to store associated file descriptors. 164 * Internally the VOP_POLL interface is used to poll for existing events. 165 * The VOP_POLL interface can also deliver a pointer to a pollhead_t structure 166 * which is used to enqueue polldat_t structures with pending events. 167 * If VOP_POLL immediately returns valid events (revents) then those events 168 * will be submitted to the event port with port_send_event(). 169 * Otherwise VOP_POLL does not return events but it delivers a pointer to a 170 * pollhead_t structure. In such a case the corresponding file system behind 171 * VOP_POLL will use the pollwakeup() function to notify about existing 172 * events. 173 */ 174 int 175 port_associate_fd(port_t *pp, int source, uintptr_t object, int events, 176 void *user) 177 { 178 port_fdcache_t *pcp; 179 int fd; 180 struct pollhead *php = NULL; 181 portfd_t *pfd; 182 polldat_t *pdp; 183 file_t *fp; 184 port_kevent_t *pkevp; 185 short revents; 186 int error = 0; 187 int active; 188 189 pcp = pp->port_queue.portq_pcp; 190 if (object > (uintptr_t)INT_MAX) 191 return (EBADFD); 192 193 fd = object; 194 195 if ((fp = getf(fd)) == NULL) 196 return (EBADFD); 197 198 mutex_enter(&pcp->pc_lock); 199 200 if (pcp->pc_hash == NULL) { 201 /* 202 * This is the first time that a fd is being associated with 203 * the current port: 204 * - create PORT_SOURCE_FD cache 205 * - associate PORT_SOURCE_FD source with the port 206 */ 207 error = port_associate_ksource(pp->port_fd, PORT_SOURCE_FD, 208 NULL, port_close_sourcefd, pp, NULL); 209 if (error) { 210 mutex_exit(&pcp->pc_lock); 211 releasef(fd); 212 return (error); 213 } 214 215 /* create polldat cache */ 216 pcp->pc_hashsize = PORTHASH_START; 217 pcp->pc_hash = kmem_zalloc(pcp->pc_hashsize * 218 sizeof (portfd_t *), KM_SLEEP); 219 pfd = NULL; 220 } else { 221 /* Check if the fd/fp is already associated with the port */ 222 pfd = port_cache_lookup_fp(pcp, fd, fp); 223 } 224 225 if (pfd == NULL) { 226 /* 227 * new entry 228 * Allocate a polldat_t structure per fd 229 * The use of the polldat_t structure to cache file descriptors 230 * is required to be able to share the pollwakeup() function 231 * with poll(2) and devpoll(4D). 232 */ 233 pfd = kmem_zalloc(sizeof (portfd_t), KM_SLEEP); 234 pdp = PFTOD(pfd); 235 pdp->pd_fd = fd; 236 pdp->pd_fp = fp; 237 pdp->pd_pcache = (void *)pcp; 238 239 /* Allocate a port event structure per fd */ 240 error = port_alloc_event_local(pp, source, PORT_ALLOC_CACHED, 241 &pdp->pd_portev); 242 if (error) { 243 kmem_free(pfd, sizeof (portfd_t)); 244 releasef(fd); 245 mutex_exit(&pcp->pc_lock); 246 return (error); 247 } 248 pkevp = pdp->pd_portev; 249 pkevp->portkev_callback = port_fd_callback; 250 pkevp->portkev_arg = pfd; 251 252 /* add portfd_t entry to the cache */ 253 port_cache_insert_fd(pcp, pdp); 254 pkevp->portkev_object = fd; 255 pkevp->portkev_user = user; 256 257 /* 258 * Add current port to the file descriptor interested list 259 * The members of the list are notified when the file descriptor 260 * is closed. 261 */ 262 addfd_port(fd, pfd); 263 } else { 264 /* 265 * The file descriptor is already associated with the port 266 */ 267 pdp = PFTOD(pfd); 268 pkevp = pdp->pd_portev; 269 270 /* 271 * Check if the re-association happens before the last 272 * submitted event of the file descriptor was retrieved. 273 * Clear the PORT_KEV_VALID flag if set. No new events 274 * should get submitted after this flag is cleared. 275 */ 276 mutex_enter(&pkevp->portkev_lock); 277 if (pkevp->portkev_flags & PORT_KEV_VALID) { 278 pkevp->portkev_flags &= ~PORT_KEV_VALID; 279 } 280 if (pkevp->portkev_flags & PORT_KEV_DONEQ) { 281 mutex_exit(&pkevp->portkev_lock); 282 /* 283 * Remove any events that where already fired 284 * for this fd and are still in the port queue. 285 */ 286 (void) port_remove_done_event(pkevp); 287 } else { 288 mutex_exit(&pkevp->portkev_lock); 289 } 290 pkevp->portkev_user = user; 291 } 292 293 pfd->pfd_thread = curthread; 294 mutex_enter(&pkevp->portkev_lock); 295 pkevp->portkev_events = 0; /* no fired events */ 296 pdp->pd_events = events; /* events associated */ 297 /* 298 * allow new events. 299 */ 300 pkevp->portkev_flags |= PORT_KEV_VALID; 301 mutex_exit(&pkevp->portkev_lock); 302 303 /* 304 * do VOP_POLL and cache this poll fd. 305 * 306 * XXX - pollrelock() logic needs to know 307 * which pollcache lock to grab. It'd be a 308 * cleaner solution if we could pass pcp as 309 * an arguement in VOP_POLL interface instead 310 * of implicitly passing it using thread_t 311 * struct. On the other hand, changing VOP_POLL 312 * interface will require all driver/file system 313 * poll routine to change. 314 */ 315 curthread->t_pollcache = (pollcache_t *)pcp; 316 error = VOP_POLL(fp->f_vnode, events, 0, &revents, &php, NULL); 317 curthread->t_pollcache = NULL; 318 319 /* 320 * The pc_lock can get dropped and reaquired in VOP_POLL. 321 * In the window pc_lock is dropped another thread in 322 * port_dissociate can remove the pfd from the port cache 323 * and free the pfd. 324 * It is also possible for another thread to sneak in and do a 325 * port_associate on the same fd during the same window. 326 * For both these cases return the current value of error. 327 * The application should take care to ensure that the threads 328 * do not race with each other for association and disassociation 329 * of the same fd. 330 */ 331 if (((pfd = port_cache_lookup_fp(pcp, fd, fp)) == NULL) || 332 (pfd->pfd_thread != curthread)) { 333 releasef(fd); 334 mutex_exit(&pcp->pc_lock); 335 return (error); 336 } 337 338 /* 339 * To keep synchronization between VOP_POLL above and 340 * polldat_associate() below, it is necessary to 341 * call VOP_POLL() again (see port_bind_pollhead()). 342 */ 343 if (error) { 344 goto errout; 345 } 346 347 if (php != NULL && (pdp->pd_php != php)) { 348 /* 349 * No events delivered yet. 350 * Bind pollhead pointer with current polldat_t structure. 351 * Sub-system will call pollwakeup() later with php as 352 * argument. 353 */ 354 error = port_bind_pollhead(&php, pdp, &revents); 355 /* 356 * The pc_lock can get dropped and reaquired in VOP_POLL. 357 * In the window pc_lock is dropped another thread in 358 * port_dissociate can remove the pfd from the port cache 359 * and free the pfd. 360 * It is also possible for another thread to sneak in and do a 361 * port_associate on the same fd during the same window. 362 * For both these cases return the current value of error. 363 * The application should take care to ensure that the threads 364 * do not race with each other for association 365 * and disassociation of the same fd. 366 */ 367 if (((pfd = port_cache_lookup_fp(pcp, fd, fp)) == NULL) || 368 (pfd->pfd_thread != curthread)) { 369 releasef(fd); 370 mutex_exit(&pcp->pc_lock); 371 return (error); 372 } 373 374 if (error) { 375 goto errout; 376 } 377 } 378 379 /* 380 * Check if new events where detected and no events have been 381 * delivered. The revents was already set after the VOP_POLL 382 * above or it was updated in port_bind_pollhead(). 383 */ 384 mutex_enter(&pkevp->portkev_lock); 385 if (revents && (pkevp->portkev_flags & PORT_KEV_VALID)) { 386 ASSERT((pkevp->portkev_flags & PORT_KEV_DONEQ) == 0); 387 pkevp->portkev_flags &= ~PORT_KEV_VALID; 388 revents = revents & (pdp->pd_events | POLLHUP | POLLERR); 389 /* send events to the event port */ 390 pkevp->portkev_events = revents; 391 /* 392 * port_send_event will release the portkev_lock mutex. 393 */ 394 port_send_event(pkevp); 395 } else { 396 mutex_exit(&pkevp->portkev_lock); 397 } 398 399 releasef(fd); 400 mutex_exit(&pcp->pc_lock); 401 return (error); 402 403 errout: 404 delfd_port(fd, pfd); 405 /* 406 * If the portkev is not valid, then an event was 407 * delivered. 408 * 409 * If an event was delivered and got picked up, then 410 * we return error = 0 treating this as a successful 411 * port associate call. The thread which received 412 * the event gets control of the object. 413 */ 414 active = 0; 415 mutex_enter(&pkevp->portkev_lock); 416 if (pkevp->portkev_flags & PORT_KEV_VALID) { 417 pkevp->portkev_flags &= ~PORT_KEV_VALID; 418 active = 1; 419 } 420 mutex_exit(&pkevp->portkev_lock); 421 422 if (!port_remove_fd_object(pfd, pp, pcp) && !active) { 423 error = 0; 424 } 425 releasef(fd); 426 mutex_exit(&pcp->pc_lock); 427 return (error); 428 } 429 430 /* 431 * The port_dissociate_fd() function dissociates the delivered file 432 * descriptor from the event port and removes already fired events. 433 * If a fd is shared between processes, all involved processes will get 434 * the same rights related to re-association of the fd with the port and 435 * retrieve of events from that fd. 436 * The process which associated the fd with a port for the first time 437 * becomes also the owner of the association. Only the owner of the 438 * association is allowed to dissociate the fd from the port. 439 */ 440 int 441 port_dissociate_fd(port_t *pp, uintptr_t object) 442 { 443 int fd; 444 port_fdcache_t *pcp; 445 portfd_t *pfd; 446 file_t *fp; 447 int active; 448 port_kevent_t *pkevp; 449 450 if (object > (uintptr_t)INT_MAX) 451 return (EBADFD); 452 453 fd = object; 454 pcp = pp->port_queue.portq_pcp; 455 456 mutex_enter(&pcp->pc_lock); 457 if (pcp->pc_hash == NULL) { 458 /* no file descriptor cache available */ 459 mutex_exit(&pcp->pc_lock); 460 return (ENOENT); 461 } 462 if ((fp = getf(fd)) == NULL) { 463 mutex_exit(&pcp->pc_lock); 464 return (EBADFD); 465 } 466 pfd = port_cache_lookup_fp(pcp, fd, fp); 467 if (pfd == NULL) { 468 releasef(fd); 469 mutex_exit(&pcp->pc_lock); 470 return (ENOENT); 471 } 472 /* only association owner is allowed to remove the association */ 473 if (curproc->p_pid != PFTOD(pfd)->pd_portev->portkev_pid) { 474 releasef(fd); 475 mutex_exit(&pcp->pc_lock); 476 return (EACCES); 477 } 478 479 /* remove port from the file descriptor interested list */ 480 delfd_port(fd, pfd); 481 482 /* 483 * Deactivate the association. No events get posted after 484 * this. 485 */ 486 pkevp = PFTOD(pfd)->pd_portev; 487 mutex_enter(&pkevp->portkev_lock); 488 if (pkevp->portkev_flags & PORT_KEV_VALID) { 489 pkevp->portkev_flags &= ~PORT_KEV_VALID; 490 active = 1; 491 } else { 492 active = 0; 493 } 494 mutex_exit(&pkevp->portkev_lock); 495 496 /* remove polldat & port event structure */ 497 if (port_remove_fd_object(pfd, pp, pcp)) { 498 /* 499 * An event was found and removed from the 500 * port done queue. This means the event has not yet 501 * been retrived. In this case we treat this as an active 502 * association. 503 */ 504 ASSERT(active == 0); 505 active = 1; 506 } 507 releasef(fd); 508 mutex_exit(&pcp->pc_lock); 509 510 /* 511 * Return ENOENT if there was no active association. 512 */ 513 return ((active ? 0 : ENOENT)); 514 } 515 516 /* 517 * Associate event port polldat_t structure with sub-system pointer to 518 * a polhead_t structure. 519 */ 520 static int 521 port_bind_pollhead(pollhead_t **php, polldat_t *pdp, short *revents) 522 { 523 int error; 524 file_t *fp; 525 526 /* break any existing association with pollhead */ 527 polldat_disassociate(pdp); 528 529 /* 530 * Before polldat_associate(), pollwakeup() will not detect a polldat 531 * entry in the ph_list and the event notification will disappear. 532 * This happens because polldat_t is still not associated with 533 * the pointer to the pollhead_t structure. 534 */ 535 polldat_associate(pdp, *php); 536 537 /* 538 * From now on event notification can be detected in pollwakeup(), 539 * Use VOP_POLL() again to check the current status of the event. 540 */ 541 fp = pdp->pd_fp; 542 curthread->t_pollcache = (pollcache_t *)pdp->pd_pcache; 543 error = VOP_POLL(fp->f_vnode, pdp->pd_events, 0, revents, php, NULL); 544 curthread->t_pollcache = NULL; 545 return (error); 546 } 547 548 /* 549 * Grow the hash table. Rehash all the elements on the hash table. 550 */ 551 static void 552 port_cache_grow_hashtbl(port_fdcache_t *pcp) 553 { 554 portfd_t **oldtbl; 555 polldat_t *pdp; 556 portfd_t *pfd; 557 polldat_t *pdp1; 558 int oldsize; 559 int i; 560 561 ASSERT(MUTEX_HELD(&pcp->pc_lock)); 562 oldsize = pcp->pc_hashsize; 563 oldtbl = pcp->pc_hash; 564 pcp->pc_hashsize *= PORTHASH_MULT; 565 pcp->pc_hash = kmem_zalloc(pcp->pc_hashsize * sizeof (portfd_t *), 566 KM_SLEEP); 567 /* 568 * rehash existing elements 569 */ 570 pcp->pc_fdcount = 0; 571 for (i = 0; i < oldsize; i++) { 572 pfd = oldtbl[i]; 573 pdp = PFTOD(pfd); 574 while (pdp != NULL) { 575 pdp1 = pdp->pd_hashnext; 576 port_cache_insert_fd(pcp, pdp); 577 pdp = pdp1; 578 } 579 } 580 kmem_free(oldtbl, oldsize * sizeof (portfd_t *)); 581 } 582 /* 583 * This routine inserts a polldat into the portcache's hash table. It 584 * may be necessary to grow the size of the hash table. 585 */ 586 static void 587 port_cache_insert_fd(port_fdcache_t *pcp, polldat_t *pdp) 588 { 589 portfd_t **bucket; 590 591 ASSERT(MUTEX_HELD(&pcp->pc_lock)); 592 if (pcp->pc_fdcount > (pcp->pc_hashsize * PORTHASH_MULT)) 593 port_cache_grow_hashtbl(pcp); 594 bucket = PORT_FD_BUCKET(pcp, pdp->pd_fd); 595 pdp->pd_hashnext = PFTOD(*bucket); 596 *bucket = PDTOF(pdp); 597 pcp->pc_fdcount++; 598 } 599 600 601 /* 602 * The port_remove_portfd() function dissociates the port from the fd 603 * and vive versa. 604 */ 605 static void 606 port_remove_portfd(polldat_t *pdp, port_fdcache_t *pcp) 607 { 608 port_t *pp; 609 file_t *fp; 610 int fd; 611 612 ASSERT(MUTEX_HELD(&pcp->pc_lock)); 613 pp = pdp->pd_portev->portkev_port; 614 fp = getf(fd = pdp->pd_fd); 615 /* 616 * If we did not get the fp for pd_fd but its portfd_t 617 * still exist in the cache, it means the pd_fd is being 618 * closed by some other thread which will also free the portfd_t. 619 */ 620 if (fp != NULL) { 621 delfd_port(pdp->pd_fd, PDTOF(pdp)); 622 (void) port_remove_fd_object(PDTOF(pdp), pp, pcp); 623 releasef(fd); 624 } 625 } 626 627 /* 628 * This function is used by port_close_sourcefd() to destroy the cache 629 * on last close. 630 */ 631 static void 632 port_pcache_destroy(port_fdcache_t *pcp) 633 { 634 ASSERT(pcp->pc_fdcount == 0); 635 kmem_free(pcp->pc_hash, sizeof (polldat_t *) * pcp->pc_hashsize); 636 mutex_destroy(&pcp->pc_lock); 637 kmem_free(pcp, sizeof (port_fdcache_t)); 638 } 639 640 /* 641 * port_close() calls this function to request the PORT_SOURCE_FD source 642 * to remove/free all resources allocated and associated with the port. 643 */ 644 /* ARGSUSED */ 645 static void 646 port_close_sourcefd(void *arg, int port, pid_t pid, int lastclose) 647 { 648 port_t *pp = arg; 649 port_fdcache_t *pcp; 650 portfd_t **hashtbl; 651 polldat_t *pdp; 652 polldat_t *pdpnext; 653 int index; 654 655 pcp = pp->port_queue.portq_pcp; 656 if (pcp == NULL) 657 /* no cache available -> nothing to do */ 658 return; 659 660 mutex_enter(&pcp->pc_lock); 661 /* 662 * Scan the cache and free all allocated portfd_t and port_kevent_t 663 * structures. 664 */ 665 hashtbl = pcp->pc_hash; 666 for (index = 0; index < pcp->pc_hashsize; index++) { 667 for (pdp = PFTOD(hashtbl[index]); pdp != NULL; pdp = pdpnext) { 668 pdpnext = pdp->pd_hashnext; 669 if (pid == pdp->pd_portev->portkev_pid) { 670 /* 671 * remove polldat + port_event_t from cache 672 * only when current process did the 673 * association. 674 */ 675 port_remove_portfd(pdp, pcp); 676 } 677 } 678 } 679 if (lastclose) { 680 /* 681 * Wait for all the portfd's to be freed. 682 * The remaining portfd_t's are the once we did not 683 * free in port_remove_portfd since some other thread 684 * is closing the fd. These threads will free the portfd_t's 685 * once we drop the pc_lock mutex. 686 */ 687 while (pcp->pc_fdcount) { 688 (void) cv_wait_sig(&pcp->pc_lclosecv, &pcp->pc_lock); 689 } 690 /* event port vnode will be destroyed -> remove everything */ 691 pp->port_queue.portq_pcp = NULL; 692 } 693 mutex_exit(&pcp->pc_lock); 694 /* 695 * last close: 696 * pollwakeup() can not further interact with this cache 697 * (all polldat structs are removed from pollhead entries). 698 */ 699 if (lastclose) 700 port_pcache_destroy(pcp); 701 } 702