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 import jdk.internal.misc.Blocker;
 39 import static sun.nio.fs.UnixNativeDispatcher.*;
 40 import static sun.nio.fs.UnixConstants.*;
 41 

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