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