1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os;
18 
19 import static android.system.OsConstants.F_DUPFD_CLOEXEC;
20 
21 import android.annotation.NonNull;
22 import android.annotation.SystemApi;
23 import android.annotation.TestApi;
24 import android.system.ErrnoException;
25 import android.system.Os;
26 
27 import java.io.Closeable;
28 import java.io.FileDescriptor;
29 
30 /**
31  * Collection representing a set of open file descriptors and an opaque data stream.
32  *
33  * @hide
34  */
35 @SystemApi
36 @TestApi
37 public final class NativeHandle implements Closeable {
38     // whether this object owns mFds
39     private boolean mOwn = false;
40     private FileDescriptor[] mFds;
41     private int[] mInts;
42 
43     /**
44      * Constructs a {@link NativeHandle} object containing
45      * zero file descriptors and an empty data stream.
46      */
NativeHandle()47     public NativeHandle() {
48         this(new FileDescriptor[0], new int[0], false);
49     }
50 
51     /**
52      * Constructs a {@link NativeHandle} object containing the given
53      * {@link FileDescriptor} object and an empty data stream.
54      */
NativeHandle(@onNull FileDescriptor descriptor, boolean own)55     public NativeHandle(@NonNull FileDescriptor descriptor, boolean own) {
56         this(new FileDescriptor[] {descriptor}, new int[0], own);
57     }
58 
59     /**
60      * Convenience method for creating a list of file descriptors.
61      *
62      * @hide
63      */
createFileDescriptorArray(@onNull int[] fds)64     private static FileDescriptor[] createFileDescriptorArray(@NonNull int[] fds) {
65         FileDescriptor[] list = new FileDescriptor[fds.length];
66         for (int i = 0; i < fds.length; i++) {
67             FileDescriptor descriptor = new FileDescriptor();
68             descriptor.setInt$(fds[i]);
69             list[i] = descriptor;
70         }
71         return list;
72     }
73 
74     /**
75      * Convenience method for instantiating a {@link NativeHandle} from JNI. It does
76      * not take ownership of the int[] params. It does not dupe the FileDescriptors.
77      *
78      * @hide
79      */
NativeHandle(@onNull int[] fds, @NonNull int[] ints, boolean own)80     private NativeHandle(@NonNull int[] fds, @NonNull int[] ints, boolean own) {
81         this(createFileDescriptorArray(fds), ints, own);
82     }
83 
84     /**
85      * Instantiate an opaque {@link NativeHandle} from fds and integers.
86      *
87      * @param own whether the fds are owned by this object and should be closed
88      */
NativeHandle(@onNull FileDescriptor[] fds, @NonNull int[] ints, boolean own)89     public NativeHandle(@NonNull FileDescriptor[] fds, @NonNull int[] ints, boolean own) {
90         mFds = fds.clone();
91         mInts = ints.clone();
92         mOwn = own;
93     }
94 
95     /**
96      * Returns whether this {@link NativeHandle} object contains a single file
97      * descriptor and nothing else.
98      *
99      * @return a boolean value
100      */
hasSingleFileDescriptor()101     public boolean hasSingleFileDescriptor() {
102         checkOpen();
103 
104         return mFds.length == 1 && mInts.length == 0;
105     }
106 
107     /**
108      * Explicitly duplicate NativeHandle (this dups all file descritptors).
109      *
110      * If this method is called, this must also be explicitly closed with
111      * {@link #close()}.
112      */
dup()113     public @NonNull NativeHandle dup() throws java.io.IOException {
114         FileDescriptor[] fds = new FileDescriptor[mFds.length];
115         try {
116             for (int i = 0; i < mFds.length; i++) {
117                 FileDescriptor newFd = new FileDescriptor();
118                 int fdint = Os.fcntlInt(mFds[i], F_DUPFD_CLOEXEC, 0);
119                 newFd.setInt$(fdint);
120                 fds[i] = newFd;
121             }
122         } catch (ErrnoException e) {
123             e.rethrowAsIOException();
124         }
125         return new NativeHandle(fds, mInts, true /*own*/);
126     }
127 
checkOpen()128     private void checkOpen() {
129         if (mFds == null) {
130             throw new IllegalStateException("NativeHandle is invalidated after close.");
131         }
132     }
133 
134     /**
135      * Closes the file descriptors if they are owned by this object.
136      *
137      * This also invalidates the object.
138      */
139     @Override
close()140     public void close() throws java.io.IOException {
141         checkOpen();
142 
143         if (mOwn) {
144             try {
145                 for (FileDescriptor fd : mFds) {
146                     Os.close(fd);
147                 }
148             } catch (ErrnoException e) {
149                 e.rethrowAsIOException();
150             }
151 
152             mOwn = false;
153         }
154 
155         mFds = null;
156         mInts = null;
157     }
158 
159     /**
160      * Returns the underlying lone file descriptor.
161      *
162      * @return a {@link FileDescriptor} object
163      * @throws IllegalStateException if this object contains either zero or
164      *         more than one file descriptor, or a non-empty data stream.
165      */
getFileDescriptor()166     public @NonNull FileDescriptor getFileDescriptor() {
167         checkOpen();
168 
169         if (!hasSingleFileDescriptor()) {
170             throw new IllegalStateException(
171                     "NativeHandle is not single file descriptor. Contents must"
172                     + " be retreived through getFileDescriptors and getInts.");
173         }
174 
175         return mFds[0];
176     }
177 
178     /**
179      * Convenience method for fetching this object's file descriptors from JNI.
180      * @return a mutable copy of the underlying file descriptors (as an int[])
181      *
182      * @hide
183      */
getFdsAsIntArray()184     private int[] getFdsAsIntArray() {
185         checkOpen();
186 
187         int numFds = mFds.length;
188         int[] fds = new int[numFds];
189 
190         for (int i = 0; i < numFds; i++) {
191             fds[i] = mFds[i].getInt$();
192         }
193 
194         return fds;
195     }
196 
197     /**
198      * Fetch file descriptors
199      *
200      * @return the fds.
201      */
getFileDescriptors()202     public @NonNull FileDescriptor[] getFileDescriptors() {
203         checkOpen();
204 
205         return mFds;
206     }
207 
208     /**
209      * Fetch opaque ints. Note: This object retains ownership of the data.
210      *
211      * @return the opaque data stream.
212      */
getInts()213     public @NonNull int[] getInts() {
214         checkOpen();
215 
216         return mInts;
217     }
218 }
219