1 /* 2 * Copyright (C) 2009 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.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertSame; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assert.fail; 25 26 import android.content.Context; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.os.Parcel; 30 import android.os.ParcelFileDescriptor; 31 import android.os.ParcelFileDescriptor.AutoCloseInputStream; 32 import android.os.Parcelable; 33 import android.os.cts.ParcelFileDescriptorPeer.FutureCloseListener; 34 import android.support.test.InstrumentationRegistry; 35 import android.support.test.runner.AndroidJUnit4; 36 import android.system.ErrnoException; 37 import android.system.Os; 38 import android.system.OsConstants; 39 import android.test.MoreAsserts; 40 41 import com.google.common.util.concurrent.AbstractFuture; 42 43 import junit.framework.ComparisonFailure; 44 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 48 import java.io.File; 49 import java.io.FileDescriptor; 50 import java.io.FileInputStream; 51 import java.io.FileOutputStream; 52 import java.io.IOException; 53 import java.io.InputStream; 54 import java.io.OutputStream; 55 import java.net.InetAddress; 56 import java.net.ServerSocket; 57 import java.net.Socket; 58 import java.util.concurrent.TimeUnit; 59 60 @RunWith(AndroidJUnit4.class) 61 public class ParcelFileDescriptorTest { 62 private static final long DURATION = 100l; 63 64 private Context getContext() { 65 return InstrumentationRegistry.getContext(); 66 } 67 68 @Test 69 public void testConstructorAndOpen() throws Exception { 70 ParcelFileDescriptor tempFile = makeParcelFileDescriptor(getContext()); 71 72 ParcelFileDescriptor pfd = new ParcelFileDescriptor(tempFile); 73 AutoCloseInputStream in = new AutoCloseInputStream(pfd); 74 try { 75 // read the data that was wrote previously 76 assertEquals(0, in.read()); 77 assertEquals(1, in.read()); 78 assertEquals(2, in.read()); 79 assertEquals(3, in.read()); 80 } finally { 81 in.close(); 82 } 83 } 84 85 private static class DoneSignal extends AbstractFuture<Void> { 86 public boolean set() { 87 return super.set(null); 88 } 89 90 @Override 91 public boolean setException(Throwable t) { 92 return super.setException(t); 93 } 94 } 95 96 @Test 97 public void testFromSocket() throws Throwable { 98 final int PORT = 12222; 99 final int DATA = 1; 100 101 final DoneSignal done = new DoneSignal(); 102 103 final Thread t = new Thread(new Runnable() { 104 @Override 105 public void run() { 106 try { 107 ServerSocket ss; 108 ss = new ServerSocket(PORT); 109 Socket sSocket = ss.accept(); 110 OutputStream out = sSocket.getOutputStream(); 111 out.write(DATA); 112 Thread.sleep(DURATION); 113 out.close(); 114 done.set(); 115 } catch (Exception e) { 116 done.setException(e); 117 } 118 } 119 }); 120 t.start(); 121 122 Thread.sleep(DURATION); 123 Socket socket; 124 socket = new Socket(InetAddress.getLocalHost(), PORT); 125 ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); 126 AutoCloseInputStream in = new AutoCloseInputStream(pfd); 127 assertEquals(DATA, in.read()); 128 in.close(); 129 socket.close(); 130 pfd.close(); 131 132 done.get(5, TimeUnit.SECONDS); 133 } 134 135 @Test 136 public void testFromData() throws IOException { 137 assertNull(ParcelFileDescriptor.fromData(null, null)); 138 byte[] data = new byte[] { 0 }; 139 assertFileDescriptorContent(data, ParcelFileDescriptor.fromData(data, null)); 140 data = new byte[] { 0, 1, 2, 3 }; 141 assertFileDescriptorContent(data, ParcelFileDescriptor.fromData(data, null)); 142 143 // Check that modifying the data does not modify the data in the FD 144 data = new byte[] { 0, 1, 2, 3 }; 145 ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(data, null); 146 data[1] = 42; 147 assertFileDescriptorContent(new byte[] { 0, 1, 2, 3 }, pfd); 148 } 149 150 private static void assertFileDescriptorContent(byte[] expected, ParcelFileDescriptor fd) 151 throws IOException { 152 assertInputStreamContent(expected, new ParcelFileDescriptor.AutoCloseInputStream(fd)); 153 } 154 155 private static void assertInputStreamContent(byte[] expected, InputStream is) 156 throws IOException { 157 try { 158 byte[] observed = new byte[expected.length]; 159 int count = is.read(observed); 160 assertEquals(expected.length, count); 161 assertEquals(-1, is.read()); 162 MoreAsserts.assertEquals(expected, observed); 163 } finally { 164 is.close(); 165 } 166 } 167 168 @Test 169 public void testFromDataSkip() throws IOException { 170 byte[] data = new byte[] { 40, 41, 42, 43, 44, 45, 46 }; 171 ParcelFileDescriptor pfd = ParcelFileDescriptor.fromData(data, null); 172 assertNotNull(pfd); 173 FileDescriptor fd = pfd.getFileDescriptor(); 174 assertNotNull(fd); 175 assertTrue(fd.valid()); 176 FileInputStream is = new FileInputStream(fd); 177 try { 178 assertEquals(1, is.skip(1)); 179 assertEquals(41, is.read()); 180 assertEquals(42, is.read()); 181 assertEquals(2, is.skip(2)); 182 assertEquals(45, is.read()); 183 assertEquals(46, is.read()); 184 assertEquals(-1, is.read()); 185 } finally { 186 is.close(); 187 } 188 } 189 190 @Test 191 public void testToString() { 192 ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); 193 assertNotNull(pfd.toString()); 194 } 195 196 @Test 197 public void testWriteToParcel() throws Exception { 198 ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext()); 199 200 Parcel pl = Parcel.obtain(); 201 pf.writeToParcel(pl, ParcelFileDescriptor.PARCELABLE_WRITE_RETURN_VALUE); 202 pl.setDataPosition(0); 203 ParcelFileDescriptor pfd = ParcelFileDescriptor.CREATOR.createFromParcel(pl); 204 AutoCloseInputStream in = new AutoCloseInputStream(pfd); 205 try { 206 // read the data that was wrote previously 207 assertEquals(0, in.read()); 208 assertEquals(1, in.read()); 209 assertEquals(2, in.read()); 210 assertEquals(3, in.read()); 211 } finally { 212 in.close(); 213 } 214 } 215 216 @Test 217 public void testClose() throws Exception { 218 ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext()); 219 AutoCloseInputStream in1 = new AutoCloseInputStream(pf); 220 try { 221 assertEquals(0, in1.read()); 222 } finally { 223 in1.close(); 224 } 225 226 pf.close(); 227 228 AutoCloseInputStream in2 = new AutoCloseInputStream(pf); 229 try { 230 assertEquals(0, in2.read()); 231 fail("Failed to throw exception."); 232 } catch (Exception e) { 233 // expected 234 } finally { 235 in2.close(); 236 } 237 } 238 239 @Test 240 public void testGetStatSize() throws Exception { 241 ParcelFileDescriptor pf = makeParcelFileDescriptor(getContext()); 242 assertTrue(pf.getStatSize() >= 0); 243 } 244 245 @Test 246 public void testGetFileDescriptor() { 247 ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); 248 assertNotNull(pfd.getFileDescriptor()); 249 250 ParcelFileDescriptor p = new ParcelFileDescriptor(pfd); 251 assertSame(pfd.getFileDescriptor(), p.getFileDescriptor()); 252 } 253 254 @Test 255 public void testDescribeContents() { 256 ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); 257 assertTrue((Parcelable.CONTENTS_FILE_DESCRIPTOR & pfd.describeContents()) != 0); 258 } 259 260 private static void assertContains(String expected, String actual) { 261 if (actual.contains(expected)) return; 262 throw new ComparisonFailure("", expected, actual); 263 } 264 265 private static void write(ParcelFileDescriptor pfd, int oneByte) throws IOException{ 266 new FileOutputStream(pfd.getFileDescriptor()).write(oneByte); 267 } 268 269 // This method is unlikely to be used by clients, as clients use ContentResolver, 270 // which builds AutoCloseInputStream under the hood rather than FileInputStream 271 // built from a raw FD. 272 // 273 // Using new FileInputStream(PFD.getFileDescriptor()) is discouraged, as error 274 // propagation is lost, and read() will never throw IOException in such case. 275 private static int read(ParcelFileDescriptor pfd) throws IOException { 276 return new FileInputStream(pfd.getFileDescriptor()).read(); 277 } 278 279 @Test 280 public void testPipeNormal() throws Exception { 281 final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe(); 282 final ParcelFileDescriptor red = pipe[0]; 283 final ParcelFileDescriptor blue = pipe[1]; 284 285 write(blue, 1); 286 assertEquals(1, read(red)); 287 288 blue.close(); 289 assertEquals(-1, read(red)); 290 red.checkError(); 291 } 292 293 // Reading should be done via AutoCloseInputStream if possible, rather than 294 // recreating a FileInputStream from a raw FD, what's done in read(PFD). 295 @Test 296 public void testPipeError_Discouraged() throws Exception { 297 final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe(); 298 final ParcelFileDescriptor red = pipe[0]; 299 final ParcelFileDescriptor blue = pipe[1]; 300 301 write(blue, 2); 302 blue.closeWithError("OMG MUFFINS"); 303 304 // Even though closed we should still drain pipe. 305 assertEquals(2, read(red)); 306 assertEquals(-1, read(red)); 307 try { 308 red.checkError(); 309 fail("expected throw!"); 310 } catch (IOException e) { 311 assertContains("OMG MUFFINS", e.getMessage()); 312 } 313 } 314 315 @Test 316 public void testPipeError() throws Exception { 317 final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createReliablePipe(); 318 final ParcelFileDescriptor red = pipe[0]; 319 final ParcelFileDescriptor blue = pipe[1]; 320 321 write(blue, 2); 322 blue.closeWithError("OMG MUFFINS"); 323 324 try (AutoCloseInputStream is = new AutoCloseInputStream(red)) { 325 is.read(); 326 is.read(); // Checks errors on EOF. 327 fail("expected throw!"); 328 } catch (IOException e) { 329 assertContains("OMG MUFFINS", e.getMessage()); 330 } 331 } 332 333 @Test 334 public void testFileNormal() throws Exception { 335 final Handler handler = new Handler(Looper.getMainLooper()); 336 final FutureCloseListener listener = new FutureCloseListener(); 337 final ParcelFileDescriptor file = ParcelFileDescriptor.open( 338 File.createTempFile("pfd", "bbq"), ParcelFileDescriptor.MODE_READ_WRITE, handler, 339 listener); 340 341 write(file, 7); 342 file.close(); 343 344 // make sure we were notified 345 assertEquals(null, listener.get()); 346 } 347 348 @Test 349 public void testFileError() throws Exception { 350 final Handler handler = new Handler(Looper.getMainLooper()); 351 final FutureCloseListener listener = new FutureCloseListener(); 352 final ParcelFileDescriptor file = ParcelFileDescriptor.open( 353 File.createTempFile("pfd", "bbq"), ParcelFileDescriptor.MODE_READ_WRITE, handler, 354 listener); 355 356 write(file, 8); 357 file.closeWithError("OMG BANANAS"); 358 359 // make sure error came through 360 assertContains("OMG BANANAS", listener.get().getMessage()); 361 } 362 363 @Test 364 public void testFileDetach() throws Exception { 365 final Handler handler = new Handler(Looper.getMainLooper()); 366 final FutureCloseListener listener = new FutureCloseListener(); 367 final ParcelFileDescriptor file = ParcelFileDescriptor.open( 368 File.createTempFile("pfd", "bbq"), ParcelFileDescriptor.MODE_READ_WRITE, handler, 369 listener); 370 371 file.detachFd(); 372 373 // make sure detach came through 374 assertContains("DETACHED", listener.get().getMessage()); 375 } 376 377 @Test 378 public void testSocketErrorAfterClose() throws Exception { 379 final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair(); 380 final ParcelFileDescriptor red = pair[0]; 381 final ParcelFileDescriptor blue = pair[1]; 382 383 // both sides throw their hands in the air 384 blue.closeWithError("BLUE RAWR"); 385 red.closeWithError("RED RAWR"); 386 387 // red noticed the blue error, but after that the comm pipe was dead so 388 // blue had no way of seeing the red error. 389 try { 390 red.checkError(); 391 fail("expected throw!"); 392 } catch (IOException e) { 393 assertContains("BLUE RAWR", e.getMessage()); 394 } 395 396 // expected to not throw; no error 397 blue.checkError(); 398 } 399 400 @Test 401 public void testSocketMultipleCheck() throws Exception { 402 final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createReliableSocketPair(); 403 final ParcelFileDescriptor red = pair[0]; 404 final ParcelFileDescriptor blue = pair[1]; 405 406 // allow checking before closed; they should all pass 407 blue.checkError(); 408 blue.checkError(); 409 blue.checkError(); 410 411 // and verify we actually see it 412 red.closeWithError("RAWR RED"); 413 try { 414 blue.checkError(); 415 fail("expected throw!"); 416 } catch (IOException e) { 417 assertContains("RAWR RED", e.getMessage()); 418 } 419 } 420 421 // http://b/21578056 422 @Test 423 public void testFileNamesWithNonBmpChars() throws Exception { 424 final File file = File.createTempFile("treble_clef_\ud834\udd1e", ".tmp"); 425 final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, 426 ParcelFileDescriptor.MODE_READ_ONLY); 427 assertNotNull(pfd); 428 pfd.close(); 429 } 430 431 @Test 432 public void testCheckFinalizerBehavior() throws Exception { 433 final Runtime runtime = Runtime.getRuntime(); 434 ParcelFileDescriptor pfd = makeParcelFileDescriptor(getContext()); 435 assertTrue(checkIsValid(pfd.getFileDescriptor())); 436 437 ParcelFileDescriptor wrappedPfd = new ParcelFileDescriptor(pfd); 438 assertTrue(checkIsValid(wrappedPfd.getFileDescriptor())); 439 440 FileDescriptor fd = pfd.getFileDescriptor(); 441 int rawFd = pfd.getFd(); 442 pfd = null; 443 assertNull(pfd); // To keep tools happy - yes we are using the write to null 444 runtime.gc(); runtime.runFinalization(); 445 assertTrue("Wrapped PFD failed to hold reference", 446 checkIsValid(wrappedPfd.getFileDescriptor())); 447 assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd)); 448 449 wrappedPfd = null; 450 assertNull(wrappedPfd); // To keep tools happy - yes we are using the write to null 451 runtime.gc(); runtime.runFinalization(); 452 // TODO: Enable this once b/65027998 is fixed 453 //assertTrue("FileDescriptor failed to hold reference", checkIsValid(fd)); 454 455 fd = null; 456 assertNull(fd); // To keep tools happy - yes we are using the write to null 457 runtime.gc(); runtime.runFinalization(); 458 459 try { 460 ParcelFileDescriptor.fromFd(rawFd); 461 fail("FD leaked"); 462 } catch (IOException ex) { 463 // Success 464 } 465 } 466 467 boolean checkIsValid(FileDescriptor fd) { 468 try { 469 Os.fstat(fd); 470 return true; 471 } catch (ErrnoException e) { 472 if (e.errno == OsConstants.EBADF) { 473 return false; 474 } else { 475 fail(e.getMessage()); 476 // not reached 477 return false; 478 } 479 } 480 } 481 482 static ParcelFileDescriptor makeParcelFileDescriptor(Context con) throws Exception { 483 final String fileName = "testParcelFileDescriptor"; 484 485 FileOutputStream fout = null; 486 487 fout = con.openFileOutput(fileName, Context.MODE_PRIVATE); 488 489 try { 490 fout.write(new byte[] { 0x0, 0x1, 0x2, 0x3 }); 491 } finally { 492 fout.close(); 493 } 494 495 File dir = con.getFilesDir(); 496 File file = new File(dir, fileName); 497 ParcelFileDescriptor pf = null; 498 499 pf = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); 500 501 return pf; 502 } 503 } 504