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