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