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
26package sun.nio.fs;
27
28import java.nio.file.*;
29import java.nio.channels.*;
30import java.io.FileDescriptor;
31import java.util.Set;
32
33import jdk.internal.misc.SharedSecrets;
34import jdk.internal.misc.JavaIOFileDescriptorAccess;
35import sun.nio.ch.FileChannelImpl;
36import sun.nio.ch.ThreadPool;
37import sun.nio.ch.SimpleAsynchronousFileChannelImpl;
38
39import static sun.nio.fs.UnixNativeDispatcher.*;
40import static sun.nio.fs.UnixConstants.*;
41
42/**
43 * Factory for FileChannels and AsynchronousFileChannels
44 */
45
46class UnixChannelFactory {
47    private static final JavaIOFileDescriptorAccess fdAccess =
48        SharedSecrets.getJavaIOFileDescriptorAccess();
49
50    protected UnixChannelFactory() {
51    }
52
53    /**
54     * Represents the flags from a user-supplied set of open options.
55     */
56    protected static class Flags {
57        boolean read;
58        boolean write;
59        boolean append;
60        boolean truncateExisting;
61        boolean noFollowLinks;
62        boolean create;
63        boolean createNew;
64        boolean deleteOnClose;
65        boolean sync;
66        boolean dsync;
67
68        static Flags toFlags(Set<? extends OpenOption> options) {
69            Flags flags = new Flags();
70            for (OpenOption option: options) {
71                if (option instanceof StandardOpenOption) {
72                    switch ((StandardOpenOption)option) {
73                        case READ : flags.read = true; break;
74                        case WRITE : flags.write = true; break;
75                        case APPEND : flags.append = true; break;
76                        case TRUNCATE_EXISTING : flags.truncateExisting = true; break;
77                        case CREATE : flags.create = true; break;
78                        case CREATE_NEW : flags.createNew = true; break;
79                        case DELETE_ON_CLOSE : flags.deleteOnClose = true; break;
80                        case SPARSE : /* ignore */ break;
81                        case SYNC : flags.sync = true; break;
82                        case DSYNC : flags.dsync = true; break;
83                        default: throw new UnsupportedOperationException();
84                    }
85                    continue;
86                }
87                if (option == LinkOption.NOFOLLOW_LINKS && O_NOFOLLOW != 0) {
88                    flags.noFollowLinks = true;
89                    continue;
90                }
91                if (option == null)
92                    throw new NullPointerException();
93               throw new UnsupportedOperationException(option + " not supported");
94            }
95            return flags;
96        }
97    }
98
99
100    /**
101     * Constructs a file channel from an existing (open) file descriptor
102     */
103    static FileChannel newFileChannel(int fd, String path, boolean reading, boolean writing) {
104        FileDescriptor fdObj = new FileDescriptor();
105        fdAccess.set(fdObj, fd);
106        return FileChannelImpl.open(fdObj, path, reading, writing, null);
107    }
108
109    /**
110     * Constructs a file channel by opening a file using a dfd/path pair
111     */
112    static FileChannel newFileChannel(int dfd,
113                                      UnixPath path,
114                                      String pathForPermissionCheck,
115                                      Set<? extends OpenOption> options,
116                                      int mode)
117        throws UnixException
118    {
119        Flags flags = Flags.toFlags(options);
120
121        // default is reading; append => writing
122        if (!flags.read && !flags.write) {
123            if (flags.append) {
124                flags.write = true;
125            } else {
126                flags.read = true;
127            }
128        }
129
130        // validation
131        if (flags.read && flags.append)
132            throw new IllegalArgumentException("READ + APPEND not allowed");
133        if (flags.append && flags.truncateExisting)
134            throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
135
136        FileDescriptor fdObj = open(dfd, path, pathForPermissionCheck, flags, mode);
137        return FileChannelImpl.open(fdObj, path.toString(), flags.read, flags.write, null);
138    }
139
140    /**
141     * Constructs a file channel by opening the given file.
142     */
143    static FileChannel newFileChannel(UnixPath path,
144                                      Set<? extends OpenOption> options,
145                                      int mode)
146        throws UnixException
147    {
148        return newFileChannel(-1, path, null, options, mode);
149    }
150
151    /**
152     * Constructs an asynchronous file channel by opening the given file.
153     */
154    static AsynchronousFileChannel newAsynchronousFileChannel(UnixPath path,
155                                                              Set<? extends OpenOption> options,
156                                                              int mode,
157                                                              ThreadPool pool)
158        throws UnixException
159    {
160        Flags flags = Flags.toFlags(options);
161
162        // default is reading
163        if (!flags.read && !flags.write) {
164            flags.read = true;
165        }
166
167        // validation
168        if (flags.append)
169            throw new UnsupportedOperationException("APPEND not allowed");
170
171        // for now use simple implementation
172        FileDescriptor fdObj = open(-1, path, null, flags, mode);
173        return SimpleAsynchronousFileChannelImpl.open(fdObj, flags.read, flags.write, pool);
174    }
175
176    /**
177     * Opens file based on parameters and options, returning a FileDescriptor
178     * encapsulating the handle to the open file.
179     */
180    protected static FileDescriptor open(int dfd,
181                                         UnixPath path,
182                                         String pathForPermissionCheck,
183                                         Flags flags,
184                                         int mode)
185        throws UnixException
186    {
187        // map to oflags
188        int oflags;
189        if (flags.read && flags.write) {
190            oflags = O_RDWR;
191        } else {
192            oflags = (flags.write) ? O_WRONLY : O_RDONLY;
193        }
194        if (flags.write) {
195            if (flags.truncateExisting)
196                oflags |= O_TRUNC;
197            if (flags.append)
198                oflags |= O_APPEND;
199
200            // create flags
201            if (flags.createNew) {
202                byte[] pathForSysCall = path.asByteArray();
203
204                // throw exception if file name is "." to avoid confusing error
205                if ((pathForSysCall[pathForSysCall.length-1] == '.') &&
206                    (pathForSysCall.length == 1 ||
207                    (pathForSysCall[pathForSysCall.length-2] == '/')))
208                {
209                    throw new UnixException(EEXIST);
210                }
211                oflags |= (O_CREAT | O_EXCL);
212            } else {
213                if (flags.create)
214                    oflags |= O_CREAT;
215            }
216        }
217
218        // follow links by default
219        boolean followLinks = true;
220        if (!flags.createNew && (flags.noFollowLinks || flags.deleteOnClose)) {
221            if (flags.deleteOnClose && O_NOFOLLOW == 0) {
222                try {
223                    if (UnixFileAttributes.get(path, false).isSymbolicLink())
224                        throw new UnixException("DELETE_ON_CLOSE specified and file is a symbolic link");
225                } catch (UnixException x) {
226                    if (!flags.create || x.errno() != ENOENT)
227                        throw x;
228                }
229            }
230            followLinks = false;
231            oflags |= O_NOFOLLOW;
232        }
233
234        if (flags.dsync)
235            oflags |= O_DSYNC;
236        if (flags.sync)
237            oflags |= O_SYNC;
238
239        // permission check before we open the file
240        SecurityManager sm = System.getSecurityManager();
241        if (sm != null) {
242            if (pathForPermissionCheck == null)
243                pathForPermissionCheck = path.getPathForPermissionCheck();
244            if (flags.read)
245                sm.checkRead(pathForPermissionCheck);
246            if (flags.write)
247                sm.checkWrite(pathForPermissionCheck);
248            if (flags.deleteOnClose)
249                sm.checkDelete(pathForPermissionCheck);
250        }
251
252        int fd;
253        try {
254            if (dfd >= 0) {
255                fd = openat(dfd, path.asByteArray(), oflags, mode);
256            } else {
257                fd = UnixNativeDispatcher.open(path, oflags, mode);
258            }
259        } catch (UnixException x) {
260            // Linux error can be EISDIR or EEXIST when file exists
261            if (flags.createNew && (x.errno() == EISDIR)) {
262                x.setError(EEXIST);
263            }
264
265            // handle ELOOP to avoid confusing message
266            if (!followLinks && (x.errno() == ELOOP)) {
267                x = new UnixException(x.getMessage() + " (NOFOLLOW_LINKS specified)");
268            }
269
270            throw x;
271        }
272
273        // unlink file immediately if delete on close. The spec is clear that
274        // an implementation cannot guarantee to unlink the correct file when
275        // replaced by an attacker after it is opened.
276        if (flags.deleteOnClose) {
277            try {
278                if (dfd >= 0) {
279                    unlinkat(dfd, path.asByteArray(), 0);
280                } else {
281                    unlink(path);
282                }
283            } catch (UnixException ignore) {
284                // best-effort
285            }
286        }
287
288        // create java.io.FileDescriptor
289        FileDescriptor fdObj = new FileDescriptor();
290        fdAccess.set(fdObj, fd);
291        fdAccess.setAppend(fdObj, flags.append);
292        return fdObj;
293    }
294}
295