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