1 /*
  2  * Copyright (c) 1994, 2023, 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 java.io;
 27 
 28 import java.nio.channels.FileChannel;
 29 import jdk.internal.access.SharedSecrets;
 30 import jdk.internal.access.JavaIOFileDescriptorAccess;
 31 import sun.nio.ch.FileChannelImpl;
 32 
 33 
 34 /**
 35  * A file output stream is an output stream for writing data to a
 36  * {@code File} or to a {@code FileDescriptor}. Whether or not
 37  * a file is available or may be created depends upon the underlying
 38  * platform.  Some platforms, in particular, allow a file to be opened
 39  * for writing by only one {@code FileOutputStream} (or other
 40  * file-writing object) at a time.  In such situations the constructors in
 41  * this class will fail if the file involved is already open.
 42  *
 43  * <p>{@code FileOutputStream} is meant for writing streams of raw bytes
 44  * such as image data. For writing streams of characters, consider using
 45  * {@code FileWriter}.
 46  *
 47  * @apiNote
 48  * The {@link #close} method should be called to release resources used by this
 49  * stream, either directly, or with the {@code try}-with-resources statement.
 50  *
 51  * @implSpec
 52  * Subclasses are responsible for the cleanup of resources acquired by the subclass.
 53  * Subclasses requiring that resource cleanup take place after a stream becomes
 54  * unreachable should use {@link java.lang.ref.Cleaner} or some other mechanism.
 55  *
 56  * @author  Arthur van Hoff
 57  * @see     java.io.File
 58  * @see     java.io.FileDescriptor
 59  * @see     java.io.FileInputStream
 60  * @see     java.nio.file.Files#newOutputStream
 61  * @since   1.0
 62  */
 63 public class FileOutputStream extends OutputStream
 64 {
 65     /**
 66      * Access to FileDescriptor internals.
 67      */
 68     private static final JavaIOFileDescriptorAccess FD_ACCESS =
 69         SharedSecrets.getJavaIOFileDescriptorAccess();
 70 
 71     /**
 72      * The system dependent file descriptor.
 73      */
 74     private final FileDescriptor fd;
 75 
 76     /**
 77      * The associated channel, initialized lazily.
 78      */
 79     private volatile FileChannel channel;
 80 
 81     /**
 82      * The path of the referenced file
 83      * (null if the stream is created with a file descriptor)
 84      */
 85     private final String path;
 86 
 87     private final Object closeLock = new Object();
 88 
 89     private volatile boolean closed;
 90 
 91     /**
 92      * Creates a file output stream to write to the file with the
 93      * specified name. A new {@code FileDescriptor} object is
 94      * created to represent this file connection.
 95      * <p>
 96      * First, if there is a security manager, its {@code checkWrite}
 97      * method is called with {@code name} as its argument.
 98      * <p>
 99      * If the file exists but is a directory rather than a regular file, does
100      * not exist but cannot be created, or cannot be opened for any other
101      * reason then a {@code FileNotFoundException} is thrown.
102      *
103      * @implSpec Invoking this constructor with the parameter {@code name} is
104      * equivalent to invoking {@link #FileOutputStream(String,boolean)
105      * new FileOutputStream(name, false)}.
106      *
107      * @param      name   the system-dependent filename
108      * @throws     FileNotFoundException  if the file exists but is a directory
109      *                   rather than a regular file, does not exist but cannot
110      *                   be created, or cannot be opened for any other reason
111      * @throws     SecurityException  if a security manager exists and its
112      *               {@code checkWrite} method denies write access
113      *               to the file.
114      * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
115      */
116     public FileOutputStream(String name) throws FileNotFoundException {
117         this(name != null ? new File(name) : null, false);
118     }
119 
120     /**
121      * Creates a file output stream to write to the file with the specified
122      * name.  If the second argument is {@code true}, then
123      * bytes will be written to the end of the file rather than the beginning.
124      * A new {@code FileDescriptor} object is created to represent this
125      * file connection.
126      * <p>
127      * First, if there is a security manager, its {@code checkWrite}
128      * method is called with {@code name} as its argument.
129      * <p>
130      * If the file exists but is a directory rather than a regular file, does
131      * not exist but cannot be created, or cannot be opened for any other
132      * reason then a {@code FileNotFoundException} is thrown.
133      *
134      * @param     name        the system-dependent file name
135      * @param     append      if {@code true}, then bytes will be written
136      *                   to the end of the file rather than the beginning
137      * @throws     FileNotFoundException  if the file exists but is a directory
138      *                   rather than a regular file, does not exist but cannot
139      *                   be created, or cannot be opened for any other reason.
140      * @throws     SecurityException  if a security manager exists and its
141      *               {@code checkWrite} method denies write access
142      *               to the file.
143      * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
144      * @since     1.1
145      */
146     public FileOutputStream(String name, boolean append)
147         throws FileNotFoundException
148     {
149         this(name != null ? new File(name) : null, append);
150     }
151 
152     /**
153      * Creates a file output stream to write to the file represented by
154      * the specified {@code File} object. A new
155      * {@code FileDescriptor} object is created to represent this
156      * file connection.
157      * <p>
158      * First, if there is a security manager, its {@code checkWrite}
159      * method is called with the path represented by the {@code file}
160      * argument as its argument.
161      * <p>
162      * If the file exists but is a directory rather than a regular file, does
163      * not exist but cannot be created, or cannot be opened for any other
164      * reason then a {@code FileNotFoundException} is thrown.
165      *
166      * @param      file               the file to be opened for writing.
167      * @throws     FileNotFoundException  if the file exists but is a directory
168      *                   rather than a regular file, does not exist but cannot
169      *                   be created, or cannot be opened for any other reason
170      * @throws     SecurityException  if a security manager exists and its
171      *               {@code checkWrite} method denies write access
172      *               to the file.
173      * @see        java.io.File#getPath()
174      * @see        java.lang.SecurityException
175      * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
176      */
177     public FileOutputStream(File file) throws FileNotFoundException {
178         this(file, false);
179     }
180 
181     /**
182      * Creates a file output stream to write to the file represented by
183      * the specified {@code File} object. If the second argument is
184      * {@code true}, then bytes will be written to the end of the file
185      * rather than the beginning. A new {@code FileDescriptor} object is
186      * created to represent this file connection.
187      * <p>
188      * First, if there is a security manager, its {@code checkWrite}
189      * method is called with the path represented by the {@code file}
190      * argument as its argument.
191      * <p>
192      * If the file exists but is a directory rather than a regular file, does
193      * not exist but cannot be created, or cannot be opened for any other
194      * reason then a {@code FileNotFoundException} is thrown.
195      *
196      * @param      file               the file to be opened for writing.
197      * @param     append      if {@code true}, then bytes will be written
198      *                   to the end of the file rather than the beginning
199      * @throws     FileNotFoundException  if the file exists but is a directory
200      *                   rather than a regular file, does not exist but cannot
201      *                   be created, or cannot be opened for any other reason
202      * @throws     SecurityException  if a security manager exists and its
203      *               {@code checkWrite} method denies write access
204      *               to the file.
205      * @see        java.io.File#getPath()
206      * @see        java.lang.SecurityException
207      * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
208      * @since 1.4
209      */
210     @SuppressWarnings("this-escape")
211     public FileOutputStream(File file, boolean append)
212         throws FileNotFoundException
213     {
214         String name = (file != null ? file.getPath() : null);
215         @SuppressWarnings("removal")
216         SecurityManager security = System.getSecurityManager();
217         if (security != null) {
218             security.checkWrite(name);
219         }
220         if (name == null) {
221             throw new NullPointerException();
222         }
223         if (file.isInvalid()) {
224             throw new FileNotFoundException("Invalid file path");
225         }
226         this.fd = new FileDescriptor();
227         fd.attach(this);
228         this.path = name;
229 
230         open(name, append);
231         FileCleanable.register(fd);   // open sets the fd, register the cleanup
232     }
233 
234     /**
235      * Creates a file output stream to write to the specified file
236      * descriptor, which represents an existing connection to an actual
237      * file in the file system.
238      * <p>
239      * First, if there is a security manager, its {@code checkWrite}
240      * method is called with the file descriptor {@code fdObj}
241      * argument as its argument.
242      * <p>
243      * If {@code fdObj} is null then a {@code NullPointerException}
244      * is thrown.
245      * <p>
246      * This constructor does not throw an exception if {@code fdObj}
247      * is {@link java.io.FileDescriptor#valid() invalid}.
248      * However, if the methods are invoked on the resulting stream to attempt
249      * I/O on the stream, an {@code IOException} is thrown.
250      *
251      * @param      fdObj   the file descriptor to be opened for writing
252      * @throws     SecurityException  if a security manager exists and its
253      *               {@code checkWrite} method denies
254      *               write access to the file descriptor
255      * @see        java.lang.SecurityManager#checkWrite(java.io.FileDescriptor)
256      */
257     @SuppressWarnings("this-escape")
258     public FileOutputStream(FileDescriptor fdObj) {
259         @SuppressWarnings("removal")
260         SecurityManager security = System.getSecurityManager();
261         if (fdObj == null) {
262             throw new NullPointerException();
263         }
264         if (security != null) {
265             security.checkWrite(fdObj);
266         }
267         this.fd = fdObj;
268         this.path = null;
269 
270         fd.attach(this);
271     }
272 
273     /**
274      * Opens a file, with the specified name, for overwriting or appending.
275      * @param name name of file to be opened
276      * @param append whether the file is to be opened in append mode
277      */
278     private native void open0(String name, boolean append)
279         throws FileNotFoundException;
280 
281     // wrap native call to allow instrumentation
282     /**
283      * Opens a file, with the specified name, for overwriting or appending.
284      * @param name name of file to be opened
285      * @param append whether the file is to be opened in append mode
286      */
287     private void open(String name, boolean append) throws FileNotFoundException {
288         open0(name, append);
289     }
290 
291     /**
292      * Writes the specified byte to this file output stream.
293      *
294      * @param   b   the byte to be written.
295      * @param   append   {@code true} if the write operation first
296      *     advances the position to the end of file
297      */
298     private native void write(int b, boolean append) throws IOException;
299 
300     /**
301      * Writes the specified byte to this file output stream. Implements
302      * the {@code write} method of {@code OutputStream}.
303      *
304      * @param      b   the byte to be written.
305      * @throws     IOException  if an I/O error occurs.
306      */
307     @Override
308     public void write(int b) throws IOException {
309         boolean append = FD_ACCESS.getAppend(fd);
310         write(b, append);
311     }
312 
313     /**
314      * Writes a sub array as a sequence of bytes.
315      * @param b the data to be written
316      * @param off the start offset in the data
317      * @param len the number of bytes that are written
318      * @param append {@code true} to first advance the position to the
319      *     end of file
320      * @throws    IOException If an I/O error has occurred.
321      */
322     private native void writeBytes(byte[] b, int off, int len, boolean append)
323         throws IOException;
324 
325     /**
326      * Writes {@code b.length} bytes from the specified byte array
327      * to this file output stream.
328      *
329      * @param      b   {@inheritDoc}
330      * @throws     IOException  {@inheritDoc}
331      */
332     @Override
333     public void write(byte[] b) throws IOException {
334         boolean append = FD_ACCESS.getAppend(fd);
335         writeBytes(b, 0, b.length, append);
336     }
337 
338     /**
339      * Writes {@code len} bytes from the specified byte array
340      * starting at offset {@code off} to this file output stream.
341      *
342      * @param      b     {@inheritDoc}
343      * @param      off   {@inheritDoc}
344      * @param      len   {@inheritDoc}
345      * @throws     IOException  if an I/O error occurs.
346      * @throws     IndexOutOfBoundsException {@inheritDoc}
347      */
348     @Override
349     public void write(byte[] b, int off, int len) throws IOException {
350         boolean append = FD_ACCESS.getAppend(fd);
351         writeBytes(b, off, len, append);
352     }
353 
354     /**
355      * Closes this file output stream and releases any system resources
356      * associated with this stream. This file output stream may no longer
357      * be used for writing bytes.
358      *
359      * <p> If this stream has an associated channel then the channel is closed
360      * as well.
361      *
362      * @apiNote
363      * Overriding {@link #close} to perform cleanup actions is reliable
364      * only when called directly or when called by try-with-resources.
365      *
366      * @implSpec
367      * Subclasses requiring that resource cleanup take place after a stream becomes
368      * unreachable should use the {@link java.lang.ref.Cleaner} mechanism.
369      *
370      * <p>
371      * If this stream has an associated channel then this method will close the
372      * channel, which in turn will close this stream. Subclasses that override
373      * this method should be prepared to handle possible reentrant invocation.
374      *
375      * @throws     IOException  if an I/O error occurs.
376      */
377     @Override
378     public void close() throws IOException {
379         if (closed) {
380             return;
381         }
382         synchronized (closeLock) {
383             if (closed) {
384                 return;
385             }
386             closed = true;
387         }
388 
389         FileChannel fc = channel;
390         if (fc != null) {
391             // possible race with getChannel(), benign since
392             // FileChannel.close is final and idempotent
393             fc.close();
394         }
395 
396         fd.closeAll(new Closeable() {
397             public void close() throws IOException {
398                fd.close();
399            }
400         });
401     }
402 
403     /**
404      * Returns the file descriptor associated with this stream.
405      *
406      * @return  the {@code FileDescriptor} object that represents
407      *          the connection to the file in the file system being used
408      *          by this {@code FileOutputStream} object.
409      *
410      * @throws     IOException  if an I/O error occurs.
411      * @see        java.io.FileDescriptor
412      */
413      public final FileDescriptor getFD()  throws IOException {
414         if (fd != null) {
415             return fd;
416         }
417         throw new IOException();
418      }
419 
420     /**
421      * Returns the unique {@link java.nio.channels.FileChannel FileChannel}
422      * object associated with this file output stream.
423      *
424      * <p> The initial {@link java.nio.channels.FileChannel#position()
425      * position} of the returned channel will be equal to the
426      * number of bytes written to the file so far unless this stream is in
427      * append mode, in which case it will be equal to the size of the file.
428      * Writing bytes to this stream will increment the channel's position
429      * accordingly.  Changing the channel's position, either explicitly or by
430      * writing, will change this stream's file position.
431      *
432      * @return  the file channel associated with this file output stream
433      *
434      * @since 1.4
435      */
436     public FileChannel getChannel() {
437         FileChannel fc = this.channel;
438         if (fc == null) {
439             synchronized (this) {
440                 fc = this.channel;
441                 if (fc == null) {
442                     fc = FileChannelImpl.open(fd, path, false, true, false, false, this);
443                     this.channel = fc;
444                     if (closed) {
445                         try {
446                             // possible race with close(), benign since
447                             // FileChannel.close is final and idempotent
448                             fc.close();
449                         } catch (IOException ioe) {
450                             throw new InternalError(ioe); // should not happen
451                         }
452                     }
453                 }
454             }
455         }
456         return fc;
457     }
458 
459     private static native void initIDs();
460 
461     static {
462         initIDs();
463     }
464 }