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 (!ExtendedOptions.SENSITIVITY_HIGH.matches(modifier) &&
 276                             !ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier) &&
 277                             !ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) {
 278                         return new UnsupportedOperationException("Modifier not supported");
 279                     }
 280                 }
 281             }
 282 
 283             UnixPath dir = (UnixPath)obj;
 284 
 285             // check file is directory
 286             UnixFileAttributes attrs = null;
 287             try {
 288                 attrs = UnixFileAttributes.get(dir, true);
 289             } catch (UnixException x) {
 290                 return x.asIOException(dir);
 291             }
 292             if (!attrs.isDirectory()) {
 293                 return new NotDirectoryException(dir.getPathForExceptionMessage());
 294             }
 295 
 296             // if already registered then update the events and return existing key
 297             UnixFileKey fileKey = attrs.fileKey();
 298             SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey);
 299             if (watchKey != null) {
 300                 try {
 301                     updateEvents(watchKey, events);
 302                 } catch (UnixException x) {
 303                     return x.asIOException(dir);
 304                 }
 305                 return watchKey;
 306             }
 307 
 308             // register directory
 309             long object = 0L;
 310             try {
 311                 object = registerImpl(dir, (FILE_MODIFIED | FILE_ATTRIB));
 312             } catch (UnixException x) {
 313                 return x.asIOException(dir);
 314             }
 315 
 316             // create watch key and insert it into maps
 317             watchKey = new SolarisWatchKey(watcher, dir, fileKey, object, events);
 318             object2Node.put(object, watchKey);
 319             fileKey2WatchKey.put(fileKey, watchKey);
 320 
 321             // register all entries in directory
 322             registerChildren(dir, watchKey, false, false);
 323 
 324             return watchKey;
 325         }
 326 
 327         // release resources for single entry
 328         void releaseChild(EntryNode node) {
 329             long object = node.object();
 330             if (object != 0L) {
 331                object2Node.remove(object);
 332                releaseObject(object, true);
 333                node.setObject(0L);
 334            }
 335         }
 336 
 337         // release resources for entries in directory
 338         void releaseChildren(SolarisWatchKey key) {
 339            for (EntryNode node: key.children().values()) {
 340                releaseChild(node);
 341            }
 342         }
 343 
 344         // cancel single key
 345         @Override
 346         void implCancelKey(WatchKey obj) {
 347            SolarisWatchKey key = (SolarisWatchKey)obj;
 348            if (key.isValid()) {
 349                fileKey2WatchKey.remove(key.getFileKey());
 350 
 351                // release resources for entries
 352                releaseChildren(key);
 353 
 354                // release resources for directory
 355                long object = key.object();
 356                object2Node.remove(object);
 357                releaseObject(object, true);
 358 
 359                // and finally invalidate the key
 360                key.invalidate();
 361            }
 362         }
 363 
 364         // close watch service
 365         @Override
 366         void implCloseAll() {
 367             // release all native resources
 368             for (Long object: object2Node.keySet()) {
 369                 releaseObject(object, true);
 370             }
 371 
 372             // invalidate all keys
 373             for (Map.Entry<UnixFileKey,SolarisWatchKey> entry: fileKey2WatchKey.entrySet()) {
 374                 entry.getValue().invalidate();
 375             }
 376 
 377             // clean-up
 378             object2Node.clear();
 379             fileKey2WatchKey.clear();
 380 
 381             // free global resources
 382             unsafe.freeMemory(bufferAddress);
 383             UnixNativeDispatcher.close(port);
 384         }
 385 
 386         /**
 387          * Poller main loop. Blocks on port_getn waiting for events and then
 388          * processes them.
 389          */
 390         @Override
 391         public void run() {
 392             try {
 393                 for (;;) {
 394                     int n = portGetn(port, bufferAddress, MAX_EVENT_COUNT);
 395                     assert n > 0;
 396 
 397                     long address = bufferAddress;
 398                     for (int i=0; i<n; i++) {
 399                         boolean shutdown = processEvent(address);
 400                         if (shutdown)
 401                             return;
 402                         address += SIZEOF_PORT_EVENT;
 403                     }
 404                 }
 405             } catch (UnixException x) {
 406                 x.printStackTrace();
 407             }
 408         }
 409 
 410         /**
 411          * Process a single port_event
 412          *
 413          * Returns true if poller thread is requested to shutdown.
 414          */
 415         boolean processEvent(long address) {
 416             // pe->portev_source
 417             short source = unsafe.getShort(address + OFFSETOF_SOURCE);
 418             // pe->portev_object
 419             long object = unsafe.getAddress(address + OFFSETOF_OBJECT);
 420             // pe->portev_events
 421             int events = unsafe.getInt(address + OFFSETOF_EVENTS);
 422 
 423             // user event is trigger to process pending requests
 424             if (source != PORT_SOURCE_FILE) {
 425                 if (source == PORT_SOURCE_USER) {
 426                     // process any pending requests
 427                     boolean shutdown = processRequests();
 428                     if (shutdown)
 429                         return true;
 430                 }
 431                 return false;
 432             }
 433 
 434             // lookup object to get Node
 435             Node node = object2Node.get(object);
 436             if (node == null) {
 437                 // should not happen
 438                 return false;
 439             }
 440 
 441             // As a workaround for 6642290 and 6636438/6636412 we don't use
 442             // FILE_EXCEPTION events to tell use not to register the file.
 443             // boolean reregister = (events & FILE_EXCEPTION) == 0;
 444             boolean reregister = true;
 445 
 446             // If node is EntryNode then event relates to entry in directory
 447             // If node is a SolarisWatchKey (DirectoryNode) then event relates
 448             // to a watched directory.
 449             boolean isDirectory = (node instanceof SolarisWatchKey);
 450             if (isDirectory) {
 451                 processDirectoryEvents((SolarisWatchKey)node, events);
 452             } else {
 453                 boolean ignore = processEntryEvents((EntryNode)node, events);
 454                 if (ignore)
 455                     reregister = false;
 456             }
 457 
 458             // need to re-associate to get further events
 459             if (reregister) {
 460                 try {
 461                     events = FILE_MODIFIED | FILE_ATTRIB;
 462                     if (!isDirectory) events |= FILE_NOFOLLOW;
 463                     portAssociate(port,
 464                                   PORT_SOURCE_FILE,
 465                                   object,
 466                                   events);
 467                 } catch (UnixException x) {
 468                     // unable to re-register
 469                     reregister = false;
 470                 }
 471             }
 472 
 473             // object is not re-registered so release resources. If
 474             // object is a watched directory then signal key
 475             if (!reregister) {
 476                 // release resources
 477                 object2Node.remove(object);
 478                 releaseObject(object, false);
 479 
 480                 // if watch key then signal it
 481                 if (isDirectory) {
 482                     SolarisWatchKey key = (SolarisWatchKey)node;
 483                     fileKey2WatchKey.remove( key.getFileKey() );
 484                     key.invalidate();
 485                     key.signal();
 486                 } else {
 487                     // if entry then remove it from parent
 488                     EntryNode entry = (EntryNode)node;
 489                     SolarisWatchKey key = (SolarisWatchKey)entry.parent();
 490                     key.removeChild(entry.name());
 491                 }
 492             }
 493 
 494             return false;
 495         }
 496 
 497         /**
 498          * Process directory events. If directory is modified then re-scan
 499          * directory to register any new entries
 500          */
 501         void processDirectoryEvents(SolarisWatchKey key, int mask) {
 502             if ((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) {
 503                 registerChildren(key.getDirectory(), key,
 504                     key.events().contains(StandardWatchEventKinds.ENTRY_CREATE),
 505                     key.events().contains(StandardWatchEventKinds.ENTRY_DELETE));
 506             }
 507         }
 508 
 509         /**
 510          * Process events for entries in registered directories. Returns {@code
 511          * true} if events are ignored because the watch key has been cancelled.
 512          */
 513         boolean processEntryEvents(EntryNode node, int mask) {
 514             SolarisWatchKey key = (SolarisWatchKey)node.parent();
 515             Set<? extends WatchEvent.Kind<?>> events = key.events();
 516             if (events == null) {
 517                 // key has been cancelled so ignore event
 518                 return true;
 519             }
 520 
 521             // entry modified
 522             if (((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) &&
 523                 events.contains(StandardWatchEventKinds.ENTRY_MODIFY))
 524             {
 525                 key.signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, node.name());
 526             }
 527 
 528 
 529             return false;
 530         }
 531 
 532         /**
 533          * Registers all entries in the given directory
 534          *
 535          * The {@code sendCreateEvents} and {@code sendDeleteEvents} parameters
 536          * indicates if ENTRY_CREATE and ENTRY_DELETE events should be queued
 537          * when new entries are found. When initially registering a directory
 538          * they will always be false. When re-scanning a directory then it
 539          * depends on if the events are enabled or not.
 540          */
 541         void registerChildren(UnixPath dir,
 542                               SolarisWatchKey parent,
 543                               boolean sendCreateEvents,
 544                               boolean sendDeleteEvents)
 545         {
 546             boolean isModifyEnabled =
 547                 parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY) ;
 548 
 549             // reset visited flag on entries so that we can detect file deletes
 550             for (EntryNode node: parent.children().values()) {
 551                 node.setVisited(false);
 552             }
 553 
 554             try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
 555                 for (Path entry: stream) {
 556                     Path name = entry.getFileName();
 557 
 558                     // skip entry if already registered
 559                     EntryNode node = parent.getChild(name);
 560                     if (node != null) {
 561                         node.setVisited(true);
 562                         continue;
 563                     }
 564 
 565                     // new entry found
 566 
 567                     long object = 0L;
 568                     int errno = 0;
 569                     boolean addNode = false;
 570 
 571                     // if ENTRY_MODIFY enabled then we register the entry for events
 572                     if (isModifyEnabled) {
 573                         try {
 574                             UnixPath path = (UnixPath)entry;
 575                             int events = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);
 576                             object = registerImpl(path, events);
 577                             addNode = true;
 578                         } catch (UnixException x) {
 579                             errno = x.errno();
 580                         }
 581                     } else {
 582                         addNode = true;
 583                     }
 584 
 585                     if (addNode) {
 586                         // create node
 587                         node = new EntryNode(object, (UnixPath)entry.getFileName(), parent);
 588                         node.setVisited(true);
 589                         // tell the parent about it
 590                         parent.addChild(entry.getFileName(), node);
 591                         if (object != 0L)
 592                             object2Node.put(object, node);
 593                     }
 594 
 595                     // send ENTRY_CREATE event for the new file
 596                     // send ENTRY_DELETE event for files that were deleted immediately
 597                     boolean deleted = (errno == ENOENT);
 598                     if (sendCreateEvents && (addNode || deleted))
 599                         parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name);
 600                     if (sendDeleteEvents && deleted)
 601                         parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);
 602 
 603                 }
 604             } catch (DirectoryIteratorException | IOException x) {
 605                 // queue OVERFLOW event so that user knows to re-scan directory
 606                 parent.signalEvent(StandardWatchEventKinds.OVERFLOW, null);
 607                 return;
 608             }
 609 
 610             // clean-up and send ENTRY_DELETE events for any entries that were
 611             // not found
 612             Iterator<Map.Entry<Path,EntryNode>> iterator =
 613                 parent.children().entrySet().iterator();
 614             while (iterator.hasNext()) {
 615                 Map.Entry<Path,EntryNode> entry = iterator.next();
 616                 EntryNode node = entry.getValue();
 617                 if (!node.isVisited()) {
 618                     long object = node.object();
 619                     if (object != 0L) {
 620                         object2Node.remove(object);
 621                         releaseObject(object, true);
 622                     }
 623                     if (sendDeleteEvents)
 624                         parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name());
 625                     iterator.remove();
 626                 }
 627             }
 628         }
 629 
 630         /**
 631          * Update watch key's events. If ENTRY_MODIFY changes to be enabled
 632          * then register each file in the directory; If ENTRY_MODIFY changed to
 633          * be disabled then unregister each file.
 634          */
 635         void updateEvents(SolarisWatchKey key, Set<? extends WatchEvent.Kind<?>> events)
 636             throws UnixException
 637         {
 638 
 639             // update events, remembering if ENTRY_MODIFY was previously
 640             // enabled or disabled.
 641             boolean oldModifyEnabled = key.events()
 642                 .contains(StandardWatchEventKinds.ENTRY_MODIFY);
 643             key.setEvents(events);
 644 
 645             // check if ENTRY_MODIFY has changed
 646             boolean newModifyEnabled = events
 647                 .contains(StandardWatchEventKinds.ENTRY_MODIFY);
 648             if (newModifyEnabled != oldModifyEnabled) {
 649                 UnixException ex = null;
 650                 for (EntryNode node: key.children().values()) {
 651                     if (newModifyEnabled) {
 652                         // register
 653                         UnixPath path = key.getDirectory().resolve(node.name());
 654                         int ev = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB);
 655                         try {
 656                             long object = registerImpl(path, ev);
 657                             object2Node.put(object, node);
 658                             node.setObject(object);
 659                         } catch (UnixException x) {
 660                             // if file has been deleted then it will be detected
 661                             // as a FILE_MODIFIED event on the directory
 662                             if (x.errno() != ENOENT) {
 663                                 ex = x;
 664                                 break;
 665                             }
 666                         }
 667                     } else {
 668                         // unregister
 669                         releaseChild(node);
 670                     }
 671                 }
 672 
 673                 // an error occurred
 674                 if (ex != null) {
 675                     releaseChildren(key);
 676                     throw ex;
 677                 }
 678             }
 679         }
 680 
 681         /**
 682          * Calls port_associate to register the given path.
 683          * Returns pointer to fileobj structure that is allocated for
 684          * the registration.
 685          */
 686         long registerImpl(UnixPath dir, int events)
 687             throws UnixException
 688         {
 689             // allocate memory for the path (file_obj->fo_name field)
 690             byte[] path = dir.getByteArrayForSysCalls();
 691             int len = path.length;
 692             long name = unsafe.allocateMemory(len+1);
 693             unsafe.copyMemory(path, Unsafe.ARRAY_BYTE_BASE_OFFSET, null,
 694                 name, (long)len);
 695             unsafe.putByte(name + len, (byte)0);
 696 
 697             // allocate memory for filedatanode structure - this is the object
 698             // to port_associate
 699             long object = unsafe.allocateMemory(SIZEOF_FILEOBJ);
 700             unsafe.setMemory(null, object, SIZEOF_FILEOBJ, (byte)0);
 701             unsafe.putAddress(object + OFFSET_FO_NAME, name);
 702 
 703             // associate the object with the port
 704             try {
 705                 portAssociate(port,
 706                               PORT_SOURCE_FILE,
 707                               object,
 708                               events);
 709             } catch (UnixException x) {
 710                 // debugging
 711                 if (x.errno() == EAGAIN) {
 712                     System.err.println("The maximum number of objects associated "+
 713                         "with the port has been reached");
 714                 }
 715 
 716                 unsafe.freeMemory(name);
 717                 unsafe.freeMemory(object);
 718                 throw x;
 719             }
 720             return object;
 721         }
 722 
 723         /**
 724          * Frees all resources for an file_obj object; optionally remove
 725          * association from port
 726          */
 727         void releaseObject(long object, boolean dissociate) {
 728             // remove association
 729             if (dissociate) {
 730                 try {
 731                     portDissociate(port, PORT_SOURCE_FILE, object);
 732                 } catch (UnixException x) {
 733                     // ignore
 734                 }
 735             }
 736 
 737             // free native memory
 738             long name = unsafe.getAddress(object + OFFSET_FO_NAME);
 739             unsafe.freeMemory(name);
 740             unsafe.freeMemory(object);
 741         }
 742     }
 743 
 744     /**
 745      * A node with native (file_obj) resources
 746      */
 747     private static interface Node {
 748         long object();
 749     }
 750 
 751     /**
 752      * A directory node with a map of the entries in the directory
 753      */
 754     private static interface DirectoryNode extends Node {
 755         void addChild(Path name, EntryNode node);
 756         void removeChild(Path name);
 757         EntryNode getChild(Path name);
 758     }
 759 
 760     /**
 761      * An implementation of a node that is an entry in a directory.
 762      */
 763     private static class EntryNode implements Node {
 764         private long object;
 765         private final UnixPath name;
 766         private final DirectoryNode parent;
 767         private boolean visited;
 768 
 769         EntryNode(long object, UnixPath name, DirectoryNode parent) {
 770             this.object = object;
 771             this.name = name;
 772             this.parent = parent;
 773         }
 774 
 775         @Override
 776         public long object() {
 777             return object;
 778         }
 779 
 780         void setObject(long ptr) {
 781             this.object = ptr;
 782         }
 783 
 784         UnixPath name() {
 785             return name;
 786         }
 787 
 788         DirectoryNode parent() {
 789             return parent;
 790         }
 791 
 792         boolean isVisited() {
 793             return visited;
 794         }
 795 
 796         void setVisited(boolean v) {
 797             this.visited = v;
 798         }
 799     }
 800 
 801     // -- native methods --
 802 
 803     private static native void init();
 804 
 805     private static native int portCreate() throws UnixException;
 806 
 807     private static native void portAssociate(int port, int source, long object, int events)
 808         throws UnixException;
 809 
 810     private static native void portDissociate(int port, int source, long object)
 811         throws UnixException;
 812 
 813     private static native void portSend(int port, int events)
 814         throws UnixException;
 815 
 816     private static native int portGetn(int port, long address, int max)
 817         throws UnixException;
 818 
 819     static {
 820         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 821             public Void run() {
 822                 System.loadLibrary("nio");
 823                 return null;
 824         }});
 825         init();
 826     }
 827 }