1 /*
   2  * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.nio.fs;
  27 
  28 import java.nio.file.*;
  29 import java.security.AccessController;
  30 import java.security.PrivilegedAction;
  31 import java.util.*;
  32 import java.io.IOException;
  33 import jdk.internal.misc.Unsafe;
  34 
  35 import static sun.nio.fs.UnixConstants.*;
  36 
  37 /**
  38  * Solaris implementation of WatchService based on file events notification
  39  * facility.
  40  */
  41 
  42 class SolarisWatchService
  43     extends AbstractWatchService
  44 {
  45     private static final Unsafe unsafe = Unsafe.getUnsafe();
  46     private static int addressSize = unsafe.addressSize();
  47 
  48     private static int dependsArch(int value32, int value64) {
  49         return (addressSize == 4) ? value32 : value64;
  50     }
  51 
  52     /*
  53      * typedef struct port_event {
  54      *     int             portev_events;
  55      *     ushort_t        portev_source;
  56      *     ushort_t        portev_pad;
  57      *     uintptr_t       portev_object;
  58      *     void            *portev_user;
  59      * } port_event_t;
  60      */
  61     private static final int SIZEOF_PORT_EVENT  = dependsArch(16, 24);
  62     private static final int OFFSETOF_EVENTS    = 0;
  63     private static final int OFFSETOF_SOURCE    = 4;
  64     private static final int OFFSETOF_OBJECT    = 8;
  65 
  66     /*
  67      * typedef struct file_obj {
  68      *     timestruc_t     fo_atime;
  69      *     timestruc_t     fo_mtime;
  70      *     timestruc_t     fo_ctime;
  71      *     uintptr_t       fo_pad[3];
  72      *     char            *fo_name;
  73      * } file_obj_t;
  74      */
  75     private static final int SIZEOF_FILEOBJ    = dependsArch(40, 80);
  76     private static final int OFFSET_FO_NAME    = dependsArch(36, 72);
  77 
  78     // port sources
  79     private static final short PORT_SOURCE_USER     = 3;
  80     private static final short PORT_SOURCE_FILE     = 7;
  81 
  82     // user-watchable events
  83     private static final int FILE_MODIFIED      = 0x00000002;
  84     private static final int FILE_ATTRIB        = 0x00000004;
  85     private static final int FILE_NOFOLLOW      = 0x10000000;
  86 
  87     // exception events
  88     private static final int FILE_DELETE        = 0x00000010;
  89     private static final int FILE_RENAME_TO     = 0x00000020;
  90     private static final int FILE_RENAME_FROM   = 0x00000040;
  91     private static final int UNMOUNTED          = 0x20000000;
  92     private static final int MOUNTEDOVER        = 0x40000000;
  93 
  94     // background thread to read change events
  95     private final Poller poller;
  96 
  97     SolarisWatchService(UnixFileSystem fs) throws IOException {
  98         int port = -1;
  99         try {
 100             port = portCreate();
 101         } catch (UnixException x) {
 102             throw new IOException(x.errorString());
 103         }
 104 
 105         this.poller = new Poller(fs, this, port);
 106         this.poller.start();
 107     }
 108 
 109     @Override
 110     WatchKey register(Path dir,
 111                       WatchEvent.Kind<?>[] events,
 112                       WatchEvent.Modifier... modifiers)
 113          throws IOException
 114     {
 115         // delegate to poller
 116         return poller.register(dir, events, modifiers);
 117     }
 118 
 119     @Override
 120     void implClose() throws IOException {
 121         // delegate to poller
 122         poller.close();
 123     }
 124 
 125     /**
 126      * WatchKey implementation
 127      */
 128     private class SolarisWatchKey extends AbstractWatchKey
 129         implements DirectoryNode
 130     {
 131         private final UnixFileKey fileKey;
 132 
 133         // pointer to native file_obj object
 134         private final long object;
 135 
 136         // events (may be changed). set to null when watch key is invalid
 137         private volatile Set<? extends WatchEvent.Kind<?>> events;
 138 
 139         // map of entries in directory; created lazily; accessed only by
 140         // poller thread.
 141         private Map<Path,EntryNode> children = new HashMap<>();
 142 
 143         SolarisWatchKey(SolarisWatchService watcher,
 144                         UnixPath dir,
 145                         UnixFileKey fileKey,
 146                         long object,
 147                         Set<? extends WatchEvent.Kind<?>> events)
 148         {
 149             super(dir, watcher);
 150             this.fileKey = fileKey;
 151             this.object = object;
 152             this.events = events;
 153         }
 154 
 155         UnixPath getDirectory() {
 156             return (UnixPath)watchable();
 157         }
 158 
 159         UnixFileKey getFileKey() {
 160             return fileKey;
 161         }
 162 
 163         @Override
 164         public long object() {
 165             return object;
 166         }
 167 
 168         void invalidate() {
 169             events = null;
 170         }
 171 
 172         Set<? extends WatchEvent.Kind<?>> events() {
 173             return events;
 174         }
 175 
 176         void setEvents(Set<? extends WatchEvent.Kind<?>> events) {
 177             this.events = events;
 178         }
 179 
 180         Map<Path,EntryNode> children() {
 181             return children;
 182         }
 183 
 184         @Override
 185         public boolean isValid() {
 186             return events != null;
 187         }
 188 
 189         @Override
 190         public void cancel() {
 191             if (isValid()) {
 192                 // delegate to poller
 193                 poller.cancel(this);
 194             }
 195         }
 196 
 197         @Override
 198         public void addChild(Path name, EntryNode node) {
 199             children.put(name, node);
 200         }
 201 
 202         @Override
 203         public void removeChild(Path name) {
 204             children.remove(name);
 205         }
 206 
 207         @Override
 208         public EntryNode getChild(Path name) {
 209             return children.get(name);
 210         }
 211     }
 212 
 213     /**
 214      * Background thread to read from port
 215      */
 216     private class Poller extends AbstractPoller {
 217 
 218         // maximum number of events to read per call to port_getn
 219         private static final int MAX_EVENT_COUNT            = 128;
 220 
 221         // events that map to ENTRY_DELETE
 222         private static final int FILE_REMOVED =
 223             (FILE_DELETE|FILE_RENAME_TO|FILE_RENAME_FROM);
 224 
 225         // events that tell us not to re-associate the object
 226         private static final int FILE_EXCEPTION =
 227             (FILE_REMOVED|UNMOUNTED|MOUNTEDOVER);
 228 
 229         // address of event buffers (used to receive events with port_getn)
 230         private final long bufferAddress;
 231 
 232         private final SolarisWatchService watcher;
 233 
 234         // the I/O port
 235         private final int port;
 236 
 237         // maps file key (dev/inode) to WatchKey
 238         private final Map<UnixFileKey,SolarisWatchKey> fileKey2WatchKey;
 239 
 240         // maps file_obj object to Node
 241         private final Map<Long,Node> object2Node;
 242 
 243         /**
 244          * Create a new instance
 245          */
 246         Poller(UnixFileSystem fs, SolarisWatchService watcher, int port) {
 247             this.watcher = watcher;
 248             this.port = port;
 249             this.bufferAddress =
 250                 unsafe.allocateMemory(SIZEOF_PORT_EVENT * MAX_EVENT_COUNT);
 251             this.fileKey2WatchKey = new HashMap<UnixFileKey,SolarisWatchKey>();
 252             this.object2Node = new HashMap<Long,Node>();
 253         }
 254 
 255         @Override
 256         void wakeup() throws IOException {
 257             // write to port to wakeup polling thread
 258             try {
 259                 portSend(port, 0);
 260             } catch (UnixException x) {
 261                 throw new IOException(x.errorString());
 262             }
 263         }
 264 
 265         @Override
 266         Object implRegister(Path obj,
 267                             Set<? extends WatchEvent.Kind<?>> events,
 268                             WatchEvent.Modifier... modifiers)
 269         {
 270             // no modifiers supported at this time
 271             if (modifiers.length > 0) {
 272                 for (WatchEvent.Modifier modifier: modifiers) {
 273                     if (modifier == null)
 274                         return new NullPointerException();
 275                     if (modifier instanceof com.sun.nio.file.SensitivityWatchEventModifier)
 276                         continue; // ignore
 277                     return new UnsupportedOperationException("Modifier not supported");
 278                 }
 279             }
 280 
 281             UnixPath dir = (UnixPath)obj;
 282 
 283             // check file is directory
 284             UnixFileAttributes attrs = null;
 285             try {
 286                 attrs = UnixFileAttributes.get(dir, true);
 287             } catch (UnixException x) {
 288                 return x.asIOException(dir);
 289             }
 290             if (!attrs.isDirectory()) {
 291                 return new NotDirectoryException(dir.getPathForExceptionMessage());
 292             }
 293 
 294             // if already registered then update the events and return existing key
 295             UnixFileKey fileKey = attrs.fileKey();
 296             SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey);
 297             if (watchKey != null) {
 298                 try {
 299                     updateEvents(watchKey, events);
 300                 } catch (UnixException x) {
 301                     return x.asIOException(dir);
 302                 }
 303                 return watchKey;
 304             }
 305 
 306             // register directory
 307             long object = 0L;
 308             try {
 309                 object = registerImpl(dir, (FILE_MODIFIED | FILE_ATTRIB));
 310             } catch (UnixException x) {
 311                 return x.asIOException(dir);
 312             }
 313 
 314             // create watch key and insert it into maps
 315             watchKey = new SolarisWatchKey(watcher, dir, fileKey, object, events);
 316             object2Node.put(object, watchKey);
 317             fileKey2WatchKey.put(fileKey, watchKey);
 318 
 319             // register all entries in directory
 320             registerChildren(dir, watchKey, false, false);
 321 
 322             return watchKey;
 323         }
 324 
 325         // release resources for single entry
 326         void releaseChild(EntryNode node) {
 327             long object = node.object();
 328             if (object != 0L) {
 329                object2Node.remove(object);
 330                releaseObject(object, true);
 331                node.setObject(0L);
 332            }
 333         }
 334 
 335         // release resources for entries in directory
 336         void releaseChildren(SolarisWatchKey key) {
 337            for (EntryNode node: key.children().values()) {
 338                releaseChild(node);
 339            }
 340         }
 341 
 342         // cancel single key
 343         @Override
 344         void implCancelKey(WatchKey obj) {
 345            SolarisWatchKey key = (SolarisWatchKey)obj;
 346            if (key.isValid()) {
 347                fileKey2WatchKey.remove(key.getFileKey());
 348 
 349                // release resources for entries
 350                releaseChildren(key);
 351 
 352                // release resources for directory
 353                long object = key.object();
 354                object2Node.remove(object);
 355                releaseObject(object, true);
 356 
 357                // and finally invalidate the key
 358                key.invalidate();
 359            }
 360         }
 361 
 362         // close watch service
 363         @Override
 364         void implCloseAll() {
 365             // release all native resources
 366             for (Long object: object2Node.keySet()) {
 367                 releaseObject(object, true);
 368             }
 369 
 370             // invalidate all keys
 371             for (Map.Entry<UnixFileKey,SolarisWatchKey> entry: fileKey2WatchKey.entrySet()) {
 372                 entry.getValue().invalidate();
 373             }
 374 
 375             // clean-up
 376             object2Node.clear();
 377             fileKey2WatchKey.clear();
 378 
 379             // free global resources
 380             unsafe.freeMemory(bufferAddress);
 381             UnixNativeDispatcher.close(port);
 382         }
 383 
 384         /**
 385          * Poller main loop. Blocks on port_getn waiting for events and then
 386          * processes them.
 387          */
 388         @Override
 389         public void run() {
 390             try {
 391                 for (;;) {
 392                     int n = portGetn(port, bufferAddress, MAX_EVENT_COUNT);
 393                     assert n > 0;
 394 
 395                     long address = bufferAddress;
 396                     for (int i=0; i<n; i++) {
 397                         boolean shutdown = processEvent(address);
 398                         if (shutdown)
 399                             return;
 400                         address += SIZEOF_PORT_EVENT;
 401                     }
 402                 }
 403             } catch (UnixException x) {
 404                 x.printStackTrace();
 405             }
 406         }
 407 
 408         /**
 409          * Process a single port_event
 410          *
 411          * Returns true if poller thread is requested to shutdown.
 412          */
 413         boolean processEvent(long address) {
 414             // pe->portev_source
 415             short source = unsafe.getShort(address + OFFSETOF_SOURCE);
 416             // pe->portev_object
 417             long object = unsafe.getAddress(address + OFFSETOF_OBJECT);
 418             // pe->portev_events
 419             int events = unsafe.getInt(address + OFFSETOF_EVENTS);
 420 
 421             // user event is trigger to process pending requests
 422             if (source != PORT_SOURCE_FILE) {
 423                 if (source == PORT_SOURCE_USER) {
 424                     // process any pending requests
 425                     boolean shutdown = processRequests();
 426                     if (shutdown)
 427                         return true;
 428                 }
 429                 return false;
 430             }
 431 
 432             // lookup object to get Node
 433             Node node = object2Node.get(object);
 434             if (node == null) {
 435                 // should not happen
 436                 return false;
 437             }
 438 
 439             // As a workaround for 6642290 and 6636438/6636412 we don't use
 440             // FILE_EXCEPTION events to tell use not to register the file.
 441             // boolean reregister = (events & FILE_EXCEPTION) == 0;
 442             boolean reregister = true;
 443 
 444             // If node is EntryNode then event relates to entry in directory
 445             // If node is a SolarisWatchKey (DirectoryNode) then event relates
 446             // to a watched directory.
 447             boolean isDirectory = (node instanceof SolarisWatchKey);
 448             if (isDirectory) {
 449                 processDirectoryEvents((SolarisWatchKey)node, events);
 450             } else {
 451                 boolean ignore = processEntryEvents((EntryNode)node, events);
 452                 if (ignore)
 453                     reregister = false;
 454             }
 455 
 456             // need to re-associate to get further events
 457             if (reregister) {
 458                 try {
 459                     events = FILE_MODIFIED | FILE_ATTRIB;
 460                     if (!isDirectory) events |= FILE_NOFOLLOW;
 461                     portAssociate(port,
 462                                   PORT_SOURCE_FILE,
 463                                   object,
 464                                   events);
 465                 } catch (UnixException x) {
 466                     // unable to re-register
 467                     reregister = false;
 468                 }
 469             }
 470 
 471             // object is not re-registered so release resources. If
 472             // object is a watched directory then signal key
 473             if (!reregister) {
 474                 // release resources
 475                 object2Node.remove(object);
 476                 releaseObject(object, false);
 477 
 478                 // if watch key then signal it
 479                 if (isDirectory) {
 480                     SolarisWatchKey key = (SolarisWatchKey)node;
 481                     fileKey2WatchKey.remove( key.getFileKey() );
 482                     key.invalidate();
 483                     key.signal();
 484                 } else {
 485                     // if entry then remove it from parent
 486                     EntryNode entry = (EntryNode)node;
 487                     SolarisWatchKey key = (SolarisWatchKey)entry.parent();
 488                     key.removeChild(entry.name());
 489                 }
 490             }
 491 
 492             return false;
 493         }
 494 
 495         /**
 496          * Process directory events. If directory is modified then re-scan
 497          * directory to register any new entries
 498          */
 499         void processDirectoryEvents(SolarisWatchKey key, int mask) {
 500             if ((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) {
 501                 registerChildren(key.getDirectory(), key,
 502                     key.events().contains(StandardWatchEventKinds.ENTRY_CREATE),
 503                     key.events().contains(StandardWatchEventKinds.ENTRY_DELETE));
 504             }
 505         }
 506 
 507         /**
 508          * Process events for entries in registered directories. Returns {@code
 509          * true} if events are ignored because the watch key has been cancelled.
 510          */
 511         boolean processEntryEvents(EntryNode node, int mask) {
 512             SolarisWatchKey key = (SolarisWatchKey)node.parent();
 513             Set<? extends WatchEvent.Kind<?>> events = key.events();
 514             if (events == null) {
 515                 // key has been cancelled so ignore event
 516                 return true;
 517             }
 518 
 519             // entry modified
 520             if (((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) &&
 521                 events.contains(StandardWatchEventKinds.ENTRY_MODIFY))
 522             {
 523                 key.signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, node.name());
 524             }
 525 
 526 
 527             return false;
 528         }
 529 
 530         /**
 531          * Registers all entries in the given directory
 532          *
 533          * The {@code sendCreateEvents} and {@code sendDeleteEvents} parameters
 534          * indicates if ENTRY_CREATE and ENTRY_DELETE events should be queued
 535          * when new entries are found. When initially registering a directory
 536          * they will always be false. When re-scanning a directory then it
 537          * depends on if the events are enabled or not.
 538          */
 539         void registerChildren(UnixPath dir,
 540                               SolarisWatchKey parent,
 541                               boolean sendCreateEvents,
 542                               boolean sendDeleteEvents)
 543         {
 544             boolean isModifyEnabled =
 545                 parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY) ;
 546 
 547             // reset visited flag on entries so that we can detect file deletes
 548             for (EntryNode node: parent.children().values()) {
 549                 node.setVisited(false);
 550             }
 551 
 552             try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
 553                 for (Path entry: stream) {
 554                     Path name = entry.getFileName();
 555 
 556                     // skip entry if already registered
 557                     EntryNode node = parent.getChild(name);
 558                     if (node != null) {
 559                         node.setVisited(true);
 560                         continue;
 561                     }
 562 
 563                     // new entry found
 564 
 565                     long object = 0L;
 566                     int errno = 0;
 567                     boolean addNode = false;
 568 
 569                     // if ENTRY_MODIFY enabled then we register the entry for events
 570                     if (isModifyEnabled) {
 571                         try {
 572                             UnixPath path = (UnixPath)entry;
 573                             int events = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);
 574                             object = registerImpl(path, events);
 575                             addNode = true;
 576                         } catch (UnixException x) {
 577                             errno = x.errno();
 578                         }
 579                     } else {
 580                         addNode = true;
 581                     }
 582 
 583                     if (addNode) {
 584                         // create node
 585                         node = new EntryNode(object, (UnixPath)entry.getFileName(), parent);
 586                         node.setVisited(true);
 587                         // tell the parent about it
 588                         parent.addChild(entry.getFileName(), node);
 589                         if (object != 0L)
 590                             object2Node.put(object, node);
 591                     }
 592 
 593                     // send ENTRY_CREATE event for the new file
 594                     // send ENTRY_DELETE event for files that were deleted immediately
 595                     boolean deleted = (errno == ENOENT);
 596                     if (sendCreateEvents && (addNode || deleted))
 597                         parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name);
 598                     if (sendDeleteEvents && deleted)
 599                         parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);
 600 
 601                 }
 602             } catch (DirectoryIteratorException | IOException x) {
 603                 // queue OVERFLOW event so that user knows to re-scan directory
 604                 parent.signalEvent(StandardWatchEventKinds.OVERFLOW, null);
 605                 return;
 606             }
 607 
 608             // clean-up and send ENTRY_DELETE events for any entries that were
 609             // not found
 610             Iterator<Map.Entry<Path,EntryNode>> iterator =
 611                 parent.children().entrySet().iterator();
 612             while (iterator.hasNext()) {
 613                 Map.Entry<Path,EntryNode> entry = iterator.next();
 614                 EntryNode node = entry.getValue();
 615                 if (!node.isVisited()) {
 616                     long object = node.object();
 617                     if (object != 0L) {
 618                         object2Node.remove(object);
 619                         releaseObject(object, true);
 620                     }
 621                     if (sendDeleteEvents)
 622                         parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name());
 623                     iterator.remove();
 624                 }
 625             }
 626         }
 627 
 628         /**
 629          * Update watch key's events. If ENTRY_MODIFY changes to be enabled
 630          * then register each file in the directory; If ENTRY_MODIFY changed to
 631          * be disabled then unregister each file.
 632          */
 633         void updateEvents(SolarisWatchKey key, Set<? extends WatchEvent.Kind<?>> events)
 634             throws UnixException
 635         {
 636 
 637             // update events, remembering if ENTRY_MODIFY was previously
 638             // enabled or disabled.
 639             boolean oldModifyEnabled = key.events()
 640                 .contains(StandardWatchEventKinds.ENTRY_MODIFY);
 641             key.setEvents(events);
 642 
 643             // check if ENTRY_MODIFY has changed
 644             boolean newModifyEnabled = events
 645                 .contains(StandardWatchEventKinds.ENTRY_MODIFY);
 646             if (newModifyEnabled != oldModifyEnabled) {
 647                 UnixException ex = null;
 648                 for (EntryNode node: key.children().values()) {
 649                     if (newModifyEnabled) {
 650                         // register
 651                         UnixPath path = key.getDirectory().resolve(node.name());
 652                         int ev = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);
 653                         try {
 654                             long object = registerImpl(path, ev);
 655                             object2Node.put(object, node);
 656                             node.setObject(object);
 657                         } catch (UnixException x) {
 658                             // if file has been deleted then it will be detected
 659                             // as a FILE_MODIFIED event on the directory
 660                             if (x.errno() != ENOENT) {
 661                                 ex = x;
 662                                 break;
 663                             }
 664                         }
 665                     } else {
 666                         // unregister
 667                         releaseChild(node);
 668                     }
 669                 }
 670 
 671                 // an error occurred
 672                 if (ex != null) {
 673                     releaseChildren(key);
 674                     throw ex;
 675                 }
 676             }
 677         }
 678 
 679         /**
 680          * Calls port_associate to register the given path.
 681          * Returns pointer to fileobj structure that is allocated for
 682          * the registration.
 683          */
 684         long registerImpl(UnixPath dir, int events)
 685             throws UnixException
 686         {
 687             // allocate memory for the path (file_obj->fo_name field)
 688             byte[] path = dir.getByteArrayForSysCalls();
 689             int len = path.length;
 690             long name = unsafe.allocateMemory(len+1);
 691             unsafe.copyMemory(path, Unsafe.ARRAY_BYTE_BASE_OFFSET, null,
 692                 name, (long)len);
 693             unsafe.putByte(name + len, (byte)0);
 694 
 695             // allocate memory for filedatanode structure - this is the object
 696             // to port_associate
 697             long object = unsafe.allocateMemory(SIZEOF_FILEOBJ);
 698             unsafe.setMemory(null, object, SIZEOF_FILEOBJ, (byte)0);
 699             unsafe.putAddress(object + OFFSET_FO_NAME, name);
 700 
 701             // associate the object with the port
 702             try {
 703                 portAssociate(port,
 704                               PORT_SOURCE_FILE,
 705                               object,
 706                               events);
 707             } catch (UnixException x) {
 708                 // debugging
 709                 if (x.errno() == EAGAIN) {
 710                     System.err.println("The maximum number of objects associated "+
 711                         "with the port has been reached");
 712                 }
 713 
 714                 unsafe.freeMemory(name);
 715                 unsafe.freeMemory(object);
 716                 throw x;
 717             }
 718             return object;
 719         }
 720 
 721         /**
 722          * Frees all resources for an file_obj object; optionally remove
 723          * association from port
 724          */
 725         void releaseObject(long object, boolean dissociate) {
 726             // remove association
 727             if (dissociate) {
 728                 try {
 729                     portDissociate(port, PORT_SOURCE_FILE, object);
 730                 } catch (UnixException x) {
 731                     // ignore
 732                 }
 733             }
 734 
 735             // free native memory
 736             long name = unsafe.getAddress(object + OFFSET_FO_NAME);
 737             unsafe.freeMemory(name);
 738             unsafe.freeMemory(object);
 739         }
 740     }
 741 
 742     /**
 743      * A node with native (file_obj) resources
 744      */
 745     private static interface Node {
 746         long object();
 747     }
 748 
 749     /**
 750      * A directory node with a map of the entries in the directory
 751      */
 752     private static interface DirectoryNode extends Node {
 753         void addChild(Path name, EntryNode node);
 754         void removeChild(Path name);
 755         EntryNode getChild(Path name);
 756     }
 757 
 758     /**
 759      * An implementation of a node that is an entry in a directory.
 760      */
 761     private static class EntryNode implements Node {
 762         private long object;
 763         private final UnixPath name;
 764         private final DirectoryNode parent;
 765         private boolean visited;
 766 
 767         EntryNode(long object, UnixPath name, DirectoryNode parent) {
 768             this.object = object;
 769             this.name = name;
 770             this.parent = parent;
 771         }
 772 
 773         @Override
 774         public long object() {
 775             return object;
 776         }
 777 
 778         void setObject(long ptr) {
 779             this.object = ptr;
 780         }
 781 
 782         UnixPath name() {
 783             return name;
 784         }
 785 
 786         DirectoryNode parent() {
 787             return parent;
 788         }
 789 
 790         boolean isVisited() {
 791             return visited;
 792         }
 793 
 794         void setVisited(boolean v) {
 795             this.visited = v;
 796         }
 797     }
 798 
 799     // -- native methods --
 800 
 801     private static native void init();
 802 
 803     private static native int portCreate() throws UnixException;
 804 
 805     private static native void portAssociate(int port, int source, long object, int events)
 806         throws UnixException;
 807 
 808     private static native void portDissociate(int port, int source, long object)
 809         throws UnixException;
 810 
 811     private static native void portSend(int port, int events)
 812         throws UnixException;
 813 
 814     private static native int portGetn(int port, long address, int max)
 815         throws UnixException;
 816 
 817     static {
 818         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 819             public Void run() {
 820                 System.loadLibrary("nio");
 821                 return null;
 822         }});
 823         init();
 824     }
 825 }