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