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 }