1 /*
   2  * Copyright (c) 2008, 2011, 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.nio.file.attribute.*;
  30 import java.nio.channels.SeekableByteChannel;
  31 import java.util.*;
  32 import java.util.concurrent.TimeUnit;
  33 import java.io.IOException;
  34 
  35 import static sun.nio.fs.UnixNativeDispatcher.*;
  36 import static sun.nio.fs.UnixConstants.*;
  37 
  38 /**
  39  * Unix implementation of SecureDirectoryStream.
  40  */
  41 
  42 class UnixSecureDirectoryStream
  43     implements SecureDirectoryStream<Path>
  44 {
  45     private final UnixDirectoryStream ds;
  46     private final int dfd;
  47 
  48     UnixSecureDirectoryStream(UnixPath dir,
  49                               long dp,
  50                               int dfd,
  51                               DirectoryStream.Filter<? super Path> filter)
  52     {
  53         this.ds = new UnixDirectoryStream(dir, dp, filter);
  54         this.dfd = dfd;
  55     }
  56 
  57     @Override
  58     public void close()
  59         throws IOException
  60     {
  61         ds.writeLock().lock();
  62         try {
  63             if (ds.closeImpl()) {
  64                 UnixNativeDispatcher.close(dfd);
  65             }
  66         } finally {
  67             ds.writeLock().unlock();
  68         }
  69     }
  70 
  71     @Override
  72     public Iterator<Path> iterator() {
  73         return ds.iterator(this);
  74     }
  75 
  76     private UnixPath getName(Path obj) {
  77         if (obj == null)
  78             throw new NullPointerException();
  79         if (!(obj instanceof UnixPath))
  80             throw new ProviderMismatchException();
  81         return (UnixPath)obj;
  82     }
  83 
  84     private boolean followLinks(LinkOption... options) {
  85         boolean followLinks = true;
  86         for (LinkOption option: options) {
  87             if (option == LinkOption.NOFOLLOW_LINKS) {
  88                 followLinks = false;
  89                 continue;
  90             }
  91             if (option == null)
  92                 throw new NullPointerException();
  93             throw new AssertionError("Should not get here");
  94         }
  95         return followLinks;
  96     }
  97 
  98     /**
  99      * Opens sub-directory in this directory
 100      */
 101     @Override
 102     public SecureDirectoryStream<Path> newDirectoryStream(Path obj,
 103                                                           LinkOption... options)
 104         throws IOException
 105     {
 106         UnixPath file = getName(obj);
 107         UnixPath child = ds.directory().resolve(file);
 108         boolean followLinks = followLinks(options);
 109 
 110         // permission check using name resolved against original path of directory
 111         SecurityManager sm = System.getSecurityManager();
 112         if (sm != null) {
 113             child.checkRead();
 114         }
 115 
 116         ds.readLock().lock();
 117         try {
 118             if (!ds.isOpen())
 119                 throw new ClosedDirectoryStreamException();
 120 
 121             // open directory and create new secure directory stream
 122             int newdfd1 = -1;
 123             int newdfd2 = -1;
 124             long ptr = 0L;
 125             try {
 126                 int flags = O_RDONLY;
 127                 if (!followLinks)
 128                     flags |= O_NOFOLLOW;
 129                 newdfd1 = openat(dfd, file.asByteArray(), flags , 0);
 130                 newdfd2 = dup(newdfd1);
 131                 ptr = fdopendir(newdfd1);
 132             } catch (UnixException x) {
 133                 if (newdfd1 != -1)
 134                     UnixNativeDispatcher.close(newdfd1);
 135                 if (newdfd2 != -1)
 136                     UnixNativeDispatcher.close(newdfd2);
 137                 if (x.errno() == UnixConstants.ENOTDIR)
 138                     throw new NotDirectoryException(file.toString());
 139                 x.rethrowAsIOException(file);
 140             }
 141             return new UnixSecureDirectoryStream(child, ptr, newdfd2, null);
 142         } finally {
 143             ds.readLock().unlock();
 144         }
 145     }
 146 
 147     /**
 148      * Opens file in this directory
 149      */
 150     @Override
 151     public SeekableByteChannel newByteChannel(Path obj,
 152                                               Set<? extends OpenOption> options,
 153                                               FileAttribute<?>... attrs)
 154         throws IOException
 155     {
 156         UnixPath file = getName(obj);
 157 
 158         int mode = UnixFileModeAttribute
 159             .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
 160 
 161         // path for permission check
 162         String pathToCheck = ds.directory().resolve(file).getPathForPermissionCheck();
 163 
 164         ds.readLock().lock();
 165         try {
 166             if (!ds.isOpen())
 167                 throw new ClosedDirectoryStreamException();
 168             try {
 169                 return UnixChannelFactory.newFileChannel(dfd, file, pathToCheck, options, mode);
 170             } catch (UnixException x) {
 171                 x.rethrowAsIOException(file);
 172                 return null; // keep compiler happy
 173             }
 174         } finally {
 175             ds.readLock().unlock();
 176         }
 177     }
 178 
 179     /**
 180      * Deletes file/directory in this directory. Works in a race-free manner
 181      * when invoked with flags.
 182      */
 183     private void implDelete(Path obj, boolean haveFlags, int flags)
 184         throws IOException
 185     {
 186         UnixPath file = getName(obj);
 187 
 188         // permission check using name resolved against original path of directory
 189         SecurityManager sm = System.getSecurityManager();
 190         if (sm != null) {
 191             ds.directory().resolve(file).checkDelete();
 192         }
 193 
 194         ds.readLock().lock();
 195         try {
 196             if (!ds.isOpen())
 197                 throw new ClosedDirectoryStreamException();
 198 
 199             if (!haveFlags) {
 200                 // need file attribute to know if file is directory. This creates
 201                 // a race in that the file may be replaced by a directory or a
 202                 // directory replaced by a file between the time we query the
 203                 // file type and unlink it.
 204                 UnixFileAttributes attrs = null;
 205                 try {
 206                     attrs = UnixFileAttributes.get(dfd, file, false);
 207                 } catch (UnixException x) {
 208                     x.rethrowAsIOException(file);
 209                 }
 210                 flags = (attrs.isDirectory()) ? AT_REMOVEDIR : 0;
 211             }
 212 
 213             try {
 214                 unlinkat(dfd, file.asByteArray(), flags);
 215             } catch (UnixException x) {
 216                 if ((flags & AT_REMOVEDIR) != 0) {
 217                     if (x.errno() == EEXIST || x.errno() == ENOTEMPTY) {
 218                         throw new DirectoryNotEmptyException(null);
 219                     }
 220                 }
 221                 x.rethrowAsIOException(file);
 222             }
 223         } finally {
 224             ds.readLock().unlock();
 225         }
 226     }
 227 
 228     @Override
 229     public void deleteFile(Path file) throws IOException {
 230         implDelete(file, true, 0);
 231     }
 232 
 233     @Override
 234     public void deleteDirectory(Path dir) throws IOException {
 235         implDelete(dir, true, AT_REMOVEDIR);
 236     }
 237 
 238     /**
 239      * Rename/move file in this directory to another (open) directory
 240      */
 241     @Override
 242     public void move(Path fromObj, SecureDirectoryStream<Path> dir, Path toObj)
 243         throws IOException
 244     {
 245         UnixPath from = getName(fromObj);
 246         UnixPath to = getName(toObj);
 247         if (dir == null)
 248             throw new NullPointerException();
 249         if (!(dir instanceof UnixSecureDirectoryStream))
 250             throw new ProviderMismatchException();
 251         UnixSecureDirectoryStream that = (UnixSecureDirectoryStream)dir;
 252 
 253         // permission check
 254         SecurityManager sm = System.getSecurityManager();
 255         if (sm != null) {
 256             this.ds.directory().resolve(from).checkWrite();
 257             that.ds.directory().resolve(to).checkWrite();
 258         }
 259 
 260         // lock ordering doesn't matter
 261         this.ds.readLock().lock();
 262         try {
 263             that.ds.readLock().lock();
 264             try {
 265                 if (!this.ds.isOpen() || !that.ds.isOpen())
 266                     throw new ClosedDirectoryStreamException();
 267                 try {
 268                     renameat(this.dfd, from.asByteArray(), that.dfd, to.asByteArray());
 269                 } catch (UnixException x) {
 270                     if (x.errno() == EXDEV) {
 271                         throw new AtomicMoveNotSupportedException(
 272                             from.toString(), to.toString(), x.errorString());
 273                     }
 274                     x.rethrowAsIOException(from, to);
 275                 }
 276             } finally {
 277                 that.ds.readLock().unlock();
 278             }
 279         } finally {
 280             this.ds.readLock().unlock();
 281         }
 282     }
 283 
 284     @SuppressWarnings("unchecked")
 285     private <V extends FileAttributeView> V getFileAttributeViewImpl(UnixPath file,
 286                                                                      Class<V> type,
 287                                                                      boolean followLinks)
 288     {
 289         if (type == null)
 290             throw new NullPointerException();
 291         Class<?> c = type;
 292         if (c == BasicFileAttributeView.class) {
 293             return (V) new BasicFileAttributeViewImpl(file, followLinks);
 294         }
 295         if (c == PosixFileAttributeView.class || c == FileOwnerAttributeView.class) {
 296             return (V) new PosixFileAttributeViewImpl(file, followLinks);
 297         }
 298         // TBD - should also support AclFileAttributeView
 299         return (V) null;
 300     }
 301 
 302     /**
 303      * Returns file attribute view bound to this directory
 304      */
 305     @Override
 306     public <V extends FileAttributeView> V getFileAttributeView(Class<V> type) {
 307         return getFileAttributeViewImpl(null, type, false);
 308     }
 309 
 310     /**
 311      * Returns file attribute view bound to dfd/filename.
 312      */
 313     @Override
 314     public <V extends FileAttributeView> V getFileAttributeView(Path obj,
 315                                                                 Class<V> type,
 316                                                                 LinkOption... options)
 317     {
 318         UnixPath file = getName(obj);
 319         boolean followLinks = followLinks(options);
 320         return getFileAttributeViewImpl(file, type, followLinks);
 321     }
 322 
 323     /**
 324      * A BasicFileAttributeView implementation that using a dfd/name pair.
 325      */
 326     private class BasicFileAttributeViewImpl
 327         implements BasicFileAttributeView
 328     {
 329         final UnixPath file;
 330         final boolean followLinks;
 331 
 332         BasicFileAttributeViewImpl(UnixPath file, boolean followLinks)
 333         {
 334             this.file = file;
 335             this.followLinks = followLinks;
 336         }
 337 
 338         int open() throws IOException {
 339             int oflags = O_RDONLY;
 340             if (!followLinks)
 341                 oflags |= O_NOFOLLOW;
 342             try {
 343                 return openat(dfd, file.asByteArray(), oflags, 0);
 344             } catch (UnixException x) {
 345                 x.rethrowAsIOException(file);
 346                 return -1; // keep compiler happy
 347             }
 348         }
 349 
 350         private void checkWriteAccess() {
 351             SecurityManager sm = System.getSecurityManager();
 352             if (sm != null) {
 353                 if (file == null) {
 354                     ds.directory().checkWrite();
 355                 } else {
 356                     ds.directory().resolve(file).checkWrite();
 357                 }
 358             }
 359         }
 360 
 361         @Override
 362         public String name() {
 363             return "basic";
 364         }
 365 
 366         @Override
 367         public BasicFileAttributes readAttributes() throws IOException {
 368             ds.readLock().lock();
 369             try {
 370                 if (!ds.isOpen())
 371                     throw new ClosedDirectoryStreamException();
 372 
 373                 SecurityManager sm = System.getSecurityManager();
 374                 if (sm != null) {
 375                     if (file == null) {
 376                         ds.directory().checkRead();
 377                     } else {
 378                         ds.directory().resolve(file).checkRead();
 379                     }
 380                 }
 381                 try {
 382                      UnixFileAttributes attrs = (file == null) ?
 383                          UnixFileAttributes.get(dfd) :
 384                          UnixFileAttributes.get(dfd, file, followLinks);
 385 
 386                      // SECURITY: must return as BasicFileAttribute
 387                      return attrs.asBasicFileAttributes();
 388                 } catch (UnixException x) {
 389                     x.rethrowAsIOException(file);
 390                     return null;    // keep compiler happy
 391                 }
 392             } finally {
 393                 ds.readLock().unlock();
 394             }
 395         }
 396 
 397         @Override
 398         public void setTimes(FileTime lastModifiedTime,
 399                              FileTime lastAccessTime,
 400                              FileTime createTime) // ignore
 401             throws IOException
 402         {
 403             checkWriteAccess();
 404 
 405             ds.readLock().lock();
 406             try {
 407                 if (!ds.isOpen())
 408                     throw new ClosedDirectoryStreamException();
 409 
 410                 int fd = (file == null) ? dfd : open();
 411                 try {
 412                     // if not changing both attributes then need existing attributes
 413                     if (lastModifiedTime == null || lastAccessTime == null) {
 414                         try {
 415                             UnixFileAttributes attrs = UnixFileAttributes.get(fd);
 416                             if (lastModifiedTime == null)
 417                                 lastModifiedTime = attrs.lastModifiedTime();
 418                             if (lastAccessTime == null)
 419                                 lastAccessTime = attrs.lastAccessTime();
 420                         } catch (UnixException x) {
 421                             x.rethrowAsIOException(file);
 422                         }
 423                     }
 424                     // update times
 425                     try {
 426                         futimes(fd,
 427                                 lastAccessTime.to(TimeUnit.MICROSECONDS),
 428                                 lastModifiedTime.to(TimeUnit.MICROSECONDS));
 429                     } catch (UnixException x) {
 430                         x.rethrowAsIOException(file);
 431                     }
 432                 } finally {
 433                     if (file != null)
 434                         UnixNativeDispatcher.close(fd);
 435                 }
 436             } finally {
 437                 ds.readLock().unlock();
 438             }
 439         }
 440     }
 441 
 442     /**
 443      * A PosixFileAttributeView implementation that using a dfd/name pair.
 444      */
 445     private class PosixFileAttributeViewImpl
 446         extends BasicFileAttributeViewImpl implements PosixFileAttributeView
 447     {
 448         PosixFileAttributeViewImpl(UnixPath file, boolean followLinks) {
 449             super(file, followLinks);
 450         }
 451 
 452         private void checkWriteAndUserAccess() {
 453             SecurityManager sm = System.getSecurityManager();
 454             if (sm != null) {
 455                 super.checkWriteAccess();
 456                 sm.checkPermission(new RuntimePermission("accessUserInformation"));
 457             }
 458         }
 459 
 460         @Override
 461         public String name() {
 462             return "posix";
 463         }
 464 
 465         @Override
 466         public PosixFileAttributes readAttributes() throws IOException {
 467             SecurityManager sm = System.getSecurityManager();
 468             if (sm != null) {
 469                 if (file == null)
 470                     ds.directory().checkRead();
 471                 else
 472                     ds.directory().resolve(file).checkRead();
 473                 sm.checkPermission(new RuntimePermission("accessUserInformation"));
 474             }
 475 
 476             ds.readLock().lock();
 477             try {
 478                 if (!ds.isOpen())
 479                     throw new ClosedDirectoryStreamException();
 480 
 481                 try {
 482                      UnixFileAttributes attrs = (file == null) ?
 483                          UnixFileAttributes.get(dfd) :
 484                          UnixFileAttributes.get(dfd, file, followLinks);
 485                      return attrs;
 486                 } catch (UnixException x) {
 487                     x.rethrowAsIOException(file);
 488                     return null;    // keep compiler happy
 489                 }
 490             } finally {
 491                 ds.readLock().unlock();
 492             }
 493         }
 494 
 495         @Override
 496         public void setPermissions(Set<PosixFilePermission> perms)
 497             throws IOException
 498         {
 499             // permission check
 500             checkWriteAndUserAccess();
 501 
 502             ds.readLock().lock();
 503             try {
 504                 if (!ds.isOpen())
 505                     throw new ClosedDirectoryStreamException();
 506 
 507                 int fd = (file == null) ? dfd : open();
 508                 try {
 509                     fchmod(fd, UnixFileModeAttribute.toUnixMode(perms));
 510                 } catch (UnixException x) {
 511                     x.rethrowAsIOException(file);
 512                 } finally {
 513                     if (file != null && fd >= 0)
 514                         UnixNativeDispatcher.close(fd);
 515                 }
 516             } finally {
 517                 ds.readLock().unlock();
 518             }
 519         }
 520 
 521         private void setOwners(int uid, int gid) throws IOException {
 522             // permission check
 523             checkWriteAndUserAccess();
 524 
 525             ds.readLock().lock();
 526             try {
 527                 if (!ds.isOpen())
 528                     throw new ClosedDirectoryStreamException();
 529 
 530                 int fd = (file == null) ? dfd : open();
 531                 try {
 532                     fchown(fd, uid, gid);
 533                 } catch (UnixException x) {
 534                     x.rethrowAsIOException(file);
 535                 } finally {
 536                     if (file != null && fd >= 0)
 537                         UnixNativeDispatcher.close(fd);
 538                 }
 539             } finally {
 540                 ds.readLock().unlock();
 541             }
 542         }
 543 
 544         @Override
 545         public UserPrincipal getOwner() throws IOException {
 546             return readAttributes().owner();
 547         }
 548 
 549         @Override
 550         public void setOwner(UserPrincipal owner)
 551             throws IOException
 552         {
 553             if (!(owner instanceof UnixUserPrincipals.User))
 554                 throw new ProviderMismatchException();
 555             if (owner instanceof UnixUserPrincipals.Group)
 556                 throw new IOException("'owner' parameter can't be a group");
 557             int uid = ((UnixUserPrincipals.User)owner).uid();
 558             setOwners(uid, -1);
 559         }
 560 
 561         @Override
 562         public void setGroup(GroupPrincipal group)
 563             throws IOException
 564         {
 565             if (!(group instanceof UnixUserPrincipals.Group))
 566                 throw new ProviderMismatchException();
 567             int gid = ((UnixUserPrincipals.Group)group).gid();
 568             setOwners(-1, gid);
 569         }
 570     }
 571 }