blob: 6e48f3b1ea7073e03b9206e46f38f2a012634a8b [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.incrementalinstall;
6
7import android.util.Log;
8
9import java.io.File;
10import java.io.FileOutputStream;
11import java.io.IOException;
12import java.nio.channels.FileLock;
13import java.util.concurrent.Callable;
14
15/**
16 * Helpers for dealing with .lock files used during install / first run.
17 */
18final class LockFile {
19 private static final String TAG = "cr.incrementalinstall";
20
21 private final File mFile;
22 private final FileOutputStream mOutputStream;
23 private final FileLock mFileLock;
24
25 private LockFile(File file, FileOutputStream outputStream, FileLock fileLock) {
26 mFile = file;
27 mOutputStream = outputStream;
28 mFileLock = fileLock;
29 }
30
31 /**
32 * Clears the lock file by writing to it (making it non-zero in length);
33 */
34 static void clearInstallerLock(File lockFile) throws IOException {
35 Log.i(TAG, "Clearing " + lockFile);
36 // On Android M+, we can't delete files in /data/local/tmp, so we write to it instead.
37 FileOutputStream os = new FileOutputStream(lockFile);
38 os.write(1);
39 os.close();
40 }
41
42 /**
43 * Waits for the given file to be non-zero in length.
44 */
45 static void waitForInstallerLock(final File file, long timeoutMs) {
46 pollingWait(new Callable<Boolean>() {
47 @Override public Boolean call() {
48 return !installerLockExists(file);
49 }
50 }, file, timeoutMs);
51 }
52
53 /**
54 * Waits for the given file to be non-zero in length.
55 */
56 private static void pollingWait(Callable<Boolean> func, File file, long timeoutMs) {
57 long pollIntervalMs = 200;
58 for (int i = 0; i < timeoutMs / pollIntervalMs; i++) {
59 try {
60 if (func.call()) {
61 if (i > 0) {
62 Log.i(TAG, "Finished waiting on lock file: " + file);
63 }
64 return;
65 } else if (i == 0) {
66 Log.i(TAG, "Waiting on lock file: " + file);
67 }
68 } catch (Exception e) {
69 throw new RuntimeException(e);
70 }
71 try {
72 Thread.sleep(pollIntervalMs);
73 } catch (InterruptedException e) {
74 // Should never happen.
75 }
76 }
77 throw new RuntimeException("Timed out waiting for lock file: " + file);
78 }
79
80 /**
81 * Returns whether the given lock file is missing or is in the locked state.
82 */
83 static boolean installerLockExists(File file) {
84 return !file.exists() || file.length() == 0;
85 }
86
87 /**
88 * Attempts to acquire a lock for the given file.
89 * @return Returns the FileLock if it was acquired, or null otherwise.
90 */
91 static LockFile acquireRuntimeLock(File file) {
92 try {
93 FileOutputStream outputStream = new FileOutputStream(file);
94 FileLock lock = outputStream.getChannel().tryLock();
95 if (lock != null) {
96 Log.i(TAG, "Created lock file: " + file);
97 return new LockFile(file, outputStream, lock);
98 }
99 outputStream.close();
100 } catch (IOException e) {
101 // Do nothing. We didn't get the lock.
102 Log.w(TAG, "Exception trying to acquire lock " + file, e);
103 }
104 return null;
105 }
106
107 /**
108 * Waits for the given file to not exist.
109 */
110 static void waitForRuntimeLock(final File file, long timeoutMs) {
111 pollingWait(new Callable<Boolean>() {
112 @Override public Boolean call() {
113 return !file.exists();
114 }
115 }, file, timeoutMs);
116 }
117
118 /**
119 * Releases and deletes the lock file.
120 */
121 void release() throws IOException {
122 Log.i(TAG, "Deleting lock file: " + mFile);
123 mFileLock.release();
124 mOutputStream.close();
125 if (!mFile.delete()) {
126 throw new IOException("Failed to delete lock file: " + mFile);
127 }
128 }
129}