1 /*
  2  * Copyright (c) 2008, 2021, 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.io.IOException;
 29 import java.nio.file.AtomicMoveNotSupportedException;
 30 import java.nio.file.CopyOption;
 31 import java.nio.file.DirectoryNotEmptyException;
 32 import java.nio.file.FileAlreadyExistsException;
 33 import java.nio.file.LinkOption;
 34 import java.nio.file.LinkPermission;
 35 import java.nio.file.StandardCopyOption;
 36 import java.util.concurrent.ExecutionException;
 37 import java.util.concurrent.TimeUnit;
 38 
 39 import static sun.nio.fs.UnixNativeDispatcher.*;
 40 import static sun.nio.fs.UnixConstants.*;
 41 
 42 
 43 /**
 44  * Unix implementation of Path#copyTo and Path#moveTo methods.
 45  */
 46 
 47 class UnixCopyFile {
 48     private UnixCopyFile() {  }
 49 
 50     // The flags that control how a file is copied or moved
 51     private static class Flags {
 52         boolean replaceExisting;
 53         boolean atomicMove;
 54         boolean followLinks;
 55         boolean interruptible;
 56 
 57         // the attributes to copy
 58         boolean copyBasicAttributes;
 59         boolean copyPosixAttributes;
 60         boolean copyNonPosixAttributes;
 61 
 62         // flags that indicate if we should fail if attributes cannot be copied
 63         boolean failIfUnableToCopyBasic;
 64         boolean failIfUnableToCopyPosix;
 65         boolean failIfUnableToCopyNonPosix;
 66 
 67         static Flags fromCopyOptions(CopyOption... options) {
 68             Flags flags = new Flags();
 69             flags.followLinks = true;
 70             for (CopyOption option: options) {
 71                 if (option == StandardCopyOption.REPLACE_EXISTING) {
 72                     flags.replaceExisting = true;
 73                     continue;
 74                 }
 75                 if (option == LinkOption.NOFOLLOW_LINKS) {
 76                     flags.followLinks = false;
 77                     continue;
 78                 }
 79                 if (option == StandardCopyOption.COPY_ATTRIBUTES) {
 80                     // copy all attributes but only fail if basic attributes
 81                     // cannot be copied
 82                     flags.copyBasicAttributes = true;
 83                     flags.copyPosixAttributes = true;
 84                     flags.copyNonPosixAttributes = true;
 85                     flags.failIfUnableToCopyBasic = true;
 86                     continue;
 87                 }
 88                 if (ExtendedOptions.INTERRUPTIBLE.matches(option)) {
 89                     flags.interruptible = true;
 90                     continue;
 91                 }
 92                 if (option == null)
 93                     throw new NullPointerException();
 94                 throw new UnsupportedOperationException("Unsupported copy option");
 95             }
 96             return flags;
 97         }
 98 
 99         static Flags fromMoveOptions(CopyOption... options) {
100             Flags flags = new Flags();
101             for (CopyOption option: options) {
102                 if (option == StandardCopyOption.ATOMIC_MOVE) {
103                     flags.atomicMove = true;
104                     continue;
105                 }
106                 if (option == StandardCopyOption.REPLACE_EXISTING) {
107                     flags.replaceExisting = true;
108                     continue;
109                 }
110                 if (option == LinkOption.NOFOLLOW_LINKS) {
111                     // ignore
112                     continue;
113                 }
114                 if (option == null)
115                     throw new NullPointerException();
116                 throw new UnsupportedOperationException("Unsupported copy option");
117             }
118 
119             // a move requires that all attributes be copied but only fail if
120             // the basic attributes cannot be copied
121             flags.copyBasicAttributes = true;
122             flags.copyPosixAttributes = true;
123             flags.copyNonPosixAttributes = true;
124             flags.failIfUnableToCopyBasic = true;
125             return flags;
126         }
127     }
128 
129     // copy directory from source to target
130     private static void copyDirectory(UnixPath source,
131                                       UnixFileAttributes attrs,
132                                       UnixPath target,
133                                       Flags flags)
134         throws IOException
135     {
136         try {
137             mkdir(target, attrs.mode());
138         } catch (UnixException x) {
139             x.rethrowAsIOException(target);
140         }
141 
142         // no attributes to copy
143         if (!flags.copyBasicAttributes &&
144             !flags.copyPosixAttributes &&
145             !flags.copyNonPosixAttributes) return;
146 
147         // open target directory if possible (this can fail when copying a
148         // directory for which we don't have read access).
149         int dfd = -1;
150         try {
151             dfd = open(target, O_RDONLY, 0);
152         } catch (UnixException x) {
153             // access to target directory required to copy named attributes
154             if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) {
155                 try { rmdir(target); } catch (UnixException ignore) { }
156                 x.rethrowAsIOException(target);
157             }
158         }
159 
160         boolean done = false;
161         try {
162             // copy owner/group/permissions
163             if (flags.copyPosixAttributes){
164                 try {
165                     if (dfd >= 0) {
166                         fchown(dfd, attrs.uid(), attrs.gid());
167                         fchmod(dfd, attrs.mode());
168                     } else {
169                         chown(target, attrs.uid(), attrs.gid());
170                         chmod(target, attrs.mode());
171                     }
172                 } catch (UnixException x) {
173                     // unable to set owner/group
174                     if (flags.failIfUnableToCopyPosix)
175                         x.rethrowAsIOException(target);
176                 }
177             }
178             // copy other attributes
179             if (flags.copyNonPosixAttributes && (dfd >= 0)) {
180                 int sfd = -1;
181                 try {
182                     sfd = open(source, O_RDONLY, 0);
183                 } catch (UnixException x) {
184                     if (flags.failIfUnableToCopyNonPosix)
185                         x.rethrowAsIOException(source);
186                 }
187                 if (sfd >= 0) {
188                     source.getFileSystem().copyNonPosixAttributes(sfd, dfd);
189                     close(sfd);
190                 }
191             }
192             // copy time stamps last
193             if (flags.copyBasicAttributes) {
194                 try {
195                     if (dfd >= 0 && futimesSupported()) {
196                         futimes(dfd,
197                                 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
198                                 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
199                     } else {
200                         utimes(target,
201                                attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
202                                attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
203                     }
204                 } catch (UnixException x) {
205                     // unable to set times
206                     if (flags.failIfUnableToCopyBasic)
207                         x.rethrowAsIOException(target);
208                 }
209             }
210             done = true;
211         } finally {
212             if (dfd >= 0)
213                 close(dfd);
214             if (!done) {
215                 // rollback
216                 try { rmdir(target); } catch (UnixException ignore) { }
217             }
218         }
219     }
220 
221     // copy regular file from source to target
222     private static void copyFile(UnixPath source,
223                                  UnixFileAttributes attrs,
224                                  UnixPath  target,
225                                  Flags flags,
226                                  long addressToPollForCancel)
227         throws IOException
228     {
229         int fi = -1;
230         try {
231             fi = open(source, O_RDONLY, 0);
232         } catch (UnixException x) {
233             x.rethrowAsIOException(source);
234         }
235 
236         try {
237             // open new file
238             int fo = -1;
239             try {
240                 fo = open(target,
241                            (O_WRONLY |
242                             O_CREAT |
243                             O_EXCL),
244                            attrs.mode());
245             } catch (UnixException x) {
246                 x.rethrowAsIOException(target);
247             }
248 
249             // set to true when file and attributes copied
250             boolean complete = false;
251             try {
252                 // transfer bytes to target file
253                 try {
254                     transfer(fo, fi, addressToPollForCancel);
255                 } catch (UnixException x) {
256                     x.rethrowAsIOException(source, target);
257                 }
258                 // copy owner/permissions
259                 if (flags.copyPosixAttributes) {
260                     try {
261                         fchown(fo, attrs.uid(), attrs.gid());
262                         fchmod(fo, attrs.mode());
263                     } catch (UnixException x) {
264                         if (flags.failIfUnableToCopyPosix)
265                             x.rethrowAsIOException(target);
266                     }
267                 }
268                 // copy non POSIX attributes (depends on file system)
269                 if (flags.copyNonPosixAttributes) {
270                     source.getFileSystem().copyNonPosixAttributes(fi, fo);
271                 }
272                 // copy time attributes
273                 if (flags.copyBasicAttributes) {
274                     try {
275                         if (futimesSupported()) {
276                             futimes(fo,
277                                     attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
278                                     attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
279                         } else {
280                             utimes(target,
281                                    attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
282                                    attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
283                         }
284                     } catch (UnixException x) {
285                         if (flags.failIfUnableToCopyBasic)
286                             x.rethrowAsIOException(target);
287                     }
288                 }
289                 complete = true;
290             } finally {
291                 close(fo);
292 
293                 // copy of file or attributes failed so rollback
294                 if (!complete) {
295                     try {
296                         unlink(target);
297                     } catch (UnixException ignore) { }
298                 }
299             }
300         } finally {
301             close(fi);
302         }
303     }
304 
305     // copy symbolic link from source to target
306     private static void copyLink(UnixPath source,
307                                  UnixFileAttributes attrs,
308                                  UnixPath  target,
309                                  Flags flags)
310         throws IOException
311     {
312         byte[] linktarget = null;
313         try {
314             linktarget = readlink(source);
315         } catch (UnixException x) {
316             x.rethrowAsIOException(source);
317         }
318         try {
319             symlink(linktarget, target);
320 
321             if (flags.copyPosixAttributes) {
322                 try {
323                     lchown(target, attrs.uid(), attrs.gid());
324                 } catch (UnixException x) {
325                     // ignore since link attributes not required to be copied
326                 }
327             }
328         } catch (UnixException x) {
329             x.rethrowAsIOException(target);
330         }
331     }
332 
333     // copy special file from source to target
334     private static void copySpecial(UnixPath source,
335                                     UnixFileAttributes attrs,
336                                     UnixPath  target,
337                                     Flags flags)
338         throws IOException
339     {
340         try {
341             mknod(target, attrs.mode(), attrs.rdev());
342         } catch (UnixException x) {
343             x.rethrowAsIOException(target);
344         }
345         boolean done = false;
346         try {
347             if (flags.copyPosixAttributes) {
348                 try {
349                     chown(target, attrs.uid(), attrs.gid());
350                     chmod(target, attrs.mode());
351                 } catch (UnixException x) {
352                     if (flags.failIfUnableToCopyPosix)
353                         x.rethrowAsIOException(target);
354                 }
355             }
356             if (flags.copyBasicAttributes) {
357                 try {
358                     utimes(target,
359                            attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
360                            attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
361                 } catch (UnixException x) {
362                     if (flags.failIfUnableToCopyBasic)
363                         x.rethrowAsIOException(target);
364                 }
365             }
366             done = true;
367         } finally {
368             if (!done) {
369                 try { unlink(target); } catch (UnixException ignore) { }
370             }
371         }
372     }
373 
374     // throw a DirectoryNotEmpty exception if appropriate
375     static void ensureEmptyDir(UnixPath dir) throws IOException {
376         try {
377             long ptr = opendir(dir);
378             try (UnixDirectoryStream stream =
379                 new UnixDirectoryStream(dir, ptr, e -> true)) {
380                 if (stream.iterator().hasNext()) {
381                     throw new DirectoryNotEmptyException(
382                         dir.getPathForExceptionMessage());
383                 }
384             }
385         } catch (UnixException e) {
386             e.rethrowAsIOException(dir);
387         }
388     }
389 
390     // move file from source to target
391     static void move(UnixPath source, UnixPath target, CopyOption... options)
392         throws IOException
393     {
394         // permission check
395         @SuppressWarnings("removal")
396         SecurityManager sm = System.getSecurityManager();
397         if (sm != null) {
398             source.checkWrite();
399             target.checkWrite();
400         }
401 
402         // translate options into flags
403         Flags flags = Flags.fromMoveOptions(options);
404 
405         // handle atomic rename case
406         if (flags.atomicMove) {
407             try {
408                 rename(source, target);
409             } catch (UnixException x) {
410                 if (x.errno() == EXDEV) {
411                     throw new AtomicMoveNotSupportedException(
412                         source.getPathForExceptionMessage(),
413                         target.getPathForExceptionMessage(),
414                         x.errorString());
415                 }
416                 x.rethrowAsIOException(source, target);
417             }
418             return;
419         }
420 
421         // move using rename or copy+delete
422         UnixFileAttributes sourceAttrs = null;
423         UnixFileAttributes targetAttrs = null;
424 
425         // get attributes of source file (don't follow links)
426         try {
427             sourceAttrs = UnixFileAttributes.get(source, false);
428         } catch (UnixException x) {
429             x.rethrowAsIOException(source);
430         }
431 
432         // get attributes of target file (don't follow links)
433         try {
434             targetAttrs = UnixFileAttributes.get(target, false);
435         } catch (UnixException x) {
436             // ignore
437         }
438         boolean targetExists = (targetAttrs != null);
439 
440         // if the target exists:
441         // 1. check if source and target are the same file
442         // 2. throw exception if REPLACE_EXISTING option is not set
443         // 3. delete target if REPLACE_EXISTING option set
444         if (targetExists) {
445             if (sourceAttrs.isSameFile(targetAttrs))
446                 return;  // nothing to do as files are identical
447             if (!flags.replaceExisting) {
448                 throw new FileAlreadyExistsException(
449                     target.getPathForExceptionMessage());
450             }
451 
452             // attempt to delete target
453             try {
454                 if (targetAttrs.isDirectory()) {
455                     rmdir(target);
456                 } else {
457                     unlink(target);
458                 }
459             } catch (UnixException x) {
460                 // target is non-empty directory that can't be replaced.
461                 if (targetAttrs.isDirectory() &&
462                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
463                 {
464                     throw new DirectoryNotEmptyException(
465                         target.getPathForExceptionMessage());
466                 }
467                 x.rethrowAsIOException(target);
468             }
469         }
470 
471         // first try rename
472         try {
473             rename(source, target);
474             return;
475         } catch (UnixException x) {
476             if (x.errno() != EXDEV && x.errno() != EISDIR) {
477                 x.rethrowAsIOException(source, target);
478             }
479         }
480 
481         // copy source to target
482         if (sourceAttrs.isDirectory()) {
483             ensureEmptyDir(source);
484             copyDirectory(source, sourceAttrs, target, flags);
485         } else {
486             if (sourceAttrs.isSymbolicLink()) {
487                 copyLink(source, sourceAttrs, target, flags);
488             } else {
489                 if (sourceAttrs.isDevice()) {
490                     copySpecial(source, sourceAttrs, target, flags);
491                 } else {
492                     copyFile(source, sourceAttrs, target, flags, 0L);
493                 }
494             }
495         }
496 
497         // delete source
498         try {
499             if (sourceAttrs.isDirectory()) {
500                 rmdir(source);
501             } else {
502                 unlink(source);
503             }
504         } catch (UnixException x) {
505             // file was copied but unable to unlink the source file so attempt
506             // to remove the target and throw a reasonable exception
507             try {
508                 if (sourceAttrs.isDirectory()) {
509                     rmdir(target);
510                 } else {
511                     unlink(target);
512                 }
513             } catch (UnixException ignore) { }
514 
515             if (sourceAttrs.isDirectory() &&
516                 (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
517             {
518                 throw new DirectoryNotEmptyException(
519                     source.getPathForExceptionMessage());
520             }
521             x.rethrowAsIOException(source);
522         }
523     }
524 
525     // copy file from source to target
526     static void copy(final UnixPath source,
527                      final UnixPath target,
528                      CopyOption... options) throws IOException
529     {
530         // permission checks
531         @SuppressWarnings("removal")
532         SecurityManager sm = System.getSecurityManager();
533         if (sm != null) {
534             source.checkRead();
535             target.checkWrite();
536         }
537 
538         // translate options into flags
539         final Flags flags = Flags.fromCopyOptions(options);
540 
541         UnixFileAttributes sourceAttrs = null;
542         UnixFileAttributes targetAttrs = null;
543 
544         // get attributes of source file
545         try {
546             sourceAttrs = UnixFileAttributes.get(source, flags.followLinks);
547         } catch (UnixException x) {
548             x.rethrowAsIOException(source);
549         }
550 
551         // if source file is symbolic link then we must check LinkPermission
552         if (sm != null && sourceAttrs.isSymbolicLink()) {
553             sm.checkPermission(new LinkPermission("symbolic"));
554         }
555 
556         // get attributes of target file (don't follow links)
557         try {
558             targetAttrs = UnixFileAttributes.get(target, false);
559         } catch (UnixException x) {
560             // ignore
561         }
562         boolean targetExists = (targetAttrs != null);
563 
564         // if the target exists:
565         // 1. check if source and target are the same file
566         // 2. throw exception if REPLACE_EXISTING option is not set
567         // 3. try to unlink the target
568         if (targetExists) {
569             if (sourceAttrs.isSameFile(targetAttrs))
570                 return;  // nothing to do as files are identical
571             if (!flags.replaceExisting)
572                 throw new FileAlreadyExistsException(
573                     target.getPathForExceptionMessage());
574             try {
575                 if (targetAttrs.isDirectory()) {
576                     rmdir(target);
577                 } else {
578                     unlink(target);
579                 }
580             } catch (UnixException x) {
581                 // target is non-empty directory that can't be replaced.
582                 if (targetAttrs.isDirectory() &&
583                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
584                 {
585                     throw new DirectoryNotEmptyException(
586                         target.getPathForExceptionMessage());
587                 }
588                 x.rethrowAsIOException(target);
589             }
590         }
591 
592         // do the copy
593         if (sourceAttrs.isDirectory()) {
594             copyDirectory(source, sourceAttrs, target, flags);
595             return;
596         }
597         if (sourceAttrs.isSymbolicLink()) {
598             copyLink(source, sourceAttrs, target, flags);
599             return;
600         }
601         if (!flags.interruptible) {
602             // non-interruptible file copy
603             copyFile(source, sourceAttrs, target, flags, 0L);
604             return;
605         }
606 
607         // interruptible file copy
608         final UnixFileAttributes attrsToCopy = sourceAttrs;
609         Cancellable copyTask = new Cancellable() {
610             @Override public void implRun() throws IOException {
611                 copyFile(source, attrsToCopy, target, flags,
612                     addressToPollForCancel());
613             }
614         };
615         try {
616             Cancellable.runInterruptibly(copyTask);
617         } catch (ExecutionException e) {
618             Throwable t = e.getCause();
619             if (t instanceof IOException)
620                 throw (IOException)t;
621             throw new IOException(t);
622         }
623     }
624 
625     // -- native methods --
626 
627     static native void transfer(int dst, int src, long addressToPollForCancel)
628         throws UnixException;
629 
630     static {
631         jdk.internal.loader.BootLoader.loadLibrary("nio");
632     }
633 
634 }