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