PersistentDataBlockService.java revision 963295ea105314e28e4ca9563aa09cb7440de4c3
1/*
2 * Copyright (C) 2014 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
17package com.android.server;
18
19import android.Manifest;
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.os.Binder;
23import android.os.IBinder;
24import android.os.RemoteException;
25import android.os.SystemProperties;
26import android.service.persistentdata.IPersistentDataBlockService;
27import android.util.Slog;
28import com.android.internal.R;
29import libcore.io.IoUtils;
30
31import java.io.DataInputStream;
32import java.io.DataOutputStream;
33import java.io.File;
34import java.io.FileInputStream;
35import java.io.FileNotFoundException;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.nio.ByteBuffer;
39import java.nio.channels.FileChannel;
40
41/**
42 * Service for reading and writing blocks to a persistent partition.
43 * This data will live across factory resets not initiated via the Settings UI.
44 * When a device is factory reset through Settings this data is wiped.
45 *
46 * Allows writing one block at a time. Namely, each time
47 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
48 * is called, it will overwite the data that was previously written on the block.
49 *
50 * Clients can query the size of the currently written block via
51 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
52 *
53 * Clients can any number of bytes from the currently written block up to its total size by invoking
54 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
55 */
56public class PersistentDataBlockService extends SystemService {
57    private static final String TAG = PersistentDataBlockService.class.getSimpleName();
58
59    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
60    private static final int HEADER_SIZE = 8;
61    // Magic number to mark block device as adhering to the format consumed by this service
62    private static final int PARTITION_TYPE_MARKER = 0x1990;
63    // Limit to 100k as blocks larger than this might cause strain on Binder.
64    // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
65    private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
66
67    private final Context mContext;
68    private final String mDataBlockFile;
69    private final int mAllowedUid;
70    private final Object mLock = new Object();
71    /*
72     * Separate lock for OEM unlock related operations as they can happen in parallel with regular
73     * block operations.
74     */
75    private final Object mOemLock = new Object();
76
77    private long mBlockDeviceSize;
78
79    public PersistentDataBlockService(Context context) {
80        super(context);
81        mContext = context;
82        mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
83        mBlockDeviceSize = -1; // Load lazily
84        String allowedPackage = context.getResources()
85                .getString(R.string.config_persistentDataPackageName);
86        PackageManager pm = mContext.getPackageManager();
87        int allowedUid = -1;
88        try {
89            allowedUid = pm.getPackageUid(allowedPackage,
90                    Binder.getCallingUserHandle().getIdentifier());
91        } catch (PackageManager.NameNotFoundException e) {
92            // not expected
93            Slog.e(TAG, "not able to find package " + allowedPackage, e);
94        }
95
96        mAllowedUid = allowedUid;
97    }
98
99    @Override
100    public void onStart() {
101        publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
102    }
103
104    private void enforceOemUnlockPermission() {
105        mContext.enforceCallingOrSelfPermission(
106                Manifest.permission.OEM_UNLOCK_STATE,
107                "Can't access OEM unlock state");
108    }
109
110    private void enforceUid(int callingUid) {
111        if (callingUid != mAllowedUid) {
112            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
113        }
114    }
115
116    private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
117        int totalDataSize;
118        int blockId = inputStream.readInt();
119        if (blockId == PARTITION_TYPE_MARKER) {
120            totalDataSize = inputStream.readInt();
121        } else {
122            totalDataSize = 0;
123        }
124        return totalDataSize;
125    }
126
127    private long getBlockDeviceSize() {
128        synchronized (mLock) {
129            if (mBlockDeviceSize == -1) {
130                mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
131            }
132        }
133
134        return mBlockDeviceSize;
135    }
136
137    private native long nativeGetBlockDeviceSize(String path);
138    private native int nativeWipe(String path);
139
140    private final IBinder mService = new IPersistentDataBlockService.Stub() {
141        @Override
142        public int write(byte[] data) throws RemoteException {
143            enforceUid(Binder.getCallingUid());
144
145            // Need to ensure we don't write over the last byte
146            long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
147            if (data.length > maxBlockSize) {
148                // partition is ~500k so shouldn't be a problem to downcast
149                return (int) -maxBlockSize;
150            }
151
152            DataOutputStream outputStream;
153            try {
154                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
155            } catch (FileNotFoundException e) {
156                Slog.e(TAG, "partition not available?", e);
157                return -1;
158            }
159
160            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
161            headerAndData.putInt(PARTITION_TYPE_MARKER);
162            headerAndData.putInt(data.length);
163            headerAndData.put(data);
164
165            try {
166                synchronized (mLock) {
167                    outputStream.write(headerAndData.array());
168                    return data.length;
169                }
170            } catch (IOException e) {
171                Slog.e(TAG, "failed writing to the persistent data block", e);
172                return -1;
173            } finally {
174                try {
175                    outputStream.close();
176                } catch (IOException e) {
177                    Slog.e(TAG, "failed closing output stream", e);
178                }
179            }
180        }
181
182        @Override
183        public byte[] read() {
184            enforceUid(Binder.getCallingUid());
185
186            DataInputStream inputStream;
187            try {
188                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
189            } catch (FileNotFoundException e) {
190                Slog.e(TAG, "partition not available?", e);
191                return null;
192            }
193
194            try {
195                synchronized (mLock) {
196                    int totalDataSize = getTotalDataSizeLocked(inputStream);
197
198                    if (totalDataSize == 0) {
199                        return new byte[0];
200                    }
201
202                    byte[] data = new byte[totalDataSize];
203                    int read = inputStream.read(data, 0, totalDataSize);
204                    if (read < totalDataSize) {
205                        // something went wrong, not returning potentially corrupt data
206                        Slog.e(TAG, "failed to read entire data block. bytes read: " +
207                                read + "/" + totalDataSize);
208                        return null;
209                    }
210                    return data;
211                }
212            } catch (IOException e) {
213                Slog.e(TAG, "failed to read data", e);
214                return null;
215            } finally {
216                try {
217                    inputStream.close();
218                } catch (IOException e) {
219                    Slog.e(TAG, "failed to close OutputStream");
220                }
221            }
222        }
223
224        @Override
225        public void wipe() {
226            enforceOemUnlockPermission();
227
228            synchronized (mLock) {
229                int ret = nativeWipe(mDataBlockFile);
230
231                if (ret < 0) {
232                    Slog.e(TAG, "failed to wipe persistent partition");
233                }
234            }
235        }
236
237        @Override
238        public void setOemUnlockEnabled(boolean enabled) {
239            enforceOemUnlockPermission();
240            FileOutputStream outputStream;
241            try {
242                outputStream = new FileOutputStream(new File(mDataBlockFile));
243            } catch (FileNotFoundException e) {
244                Slog.e(TAG, "parition not available", e);
245                return;
246            }
247
248            try {
249                FileChannel channel = outputStream.getChannel();
250
251                channel.position(getBlockDeviceSize() - 1);
252
253                ByteBuffer data = ByteBuffer.allocate(1);
254                data.put(enabled ? (byte) 1 : (byte) 0);
255                data.flip();
256
257                synchronized (mOemLock) {
258                    channel.write(data);
259                }
260            } catch (IOException e) {
261                Slog.e(TAG, "unable to access persistent partition", e);
262            } finally {
263                IoUtils.closeQuietly(outputStream);
264            }
265        }
266
267        @Override
268        public boolean getOemUnlockEnabled() {
269            enforceOemUnlockPermission();
270            DataInputStream inputStream;
271            try {
272                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
273            } catch (FileNotFoundException e) {
274                Slog.e(TAG, "partition not available");
275                return false;
276            }
277
278            try {
279                inputStream.skip(getBlockDeviceSize() - 1);
280                synchronized (mOemLock) {
281                    return inputStream.readByte() != 0;
282                }
283            } catch (IOException e) {
284                Slog.e(TAG, "unable to access persistent partition", e);
285                return false;
286            } finally {
287                IoUtils.closeQuietly(inputStream);
288            }
289        }
290
291        @Override
292        public int getDataBlockSize() {
293            enforceUid(Binder.getCallingUid());
294
295            DataInputStream inputStream;
296            try {
297                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
298            } catch (FileNotFoundException e) {
299                Slog.e(TAG, "partition not available");
300                return 0;
301            }
302
303            try {
304                synchronized (mLock) {
305                    return getTotalDataSizeLocked(inputStream);
306                }
307            } catch (IOException e) {
308                Slog.e(TAG, "error reading data block size");
309                return 0;
310            } finally {
311                IoUtils.closeQuietly(inputStream);
312            }
313        }
314
315        @Override
316        public long getMaximumDataBlockSize() {
317            long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
318            return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
319        }
320
321    };
322}
323