PersistentDataBlockService.java revision 68d4acd205e8c2da524e62734ca42847306cc029
1package com.android.server;
2
3import android.Manifest;
4import android.content.Context;
5import android.content.pm.PackageManager;
6import android.os.Binder;
7import android.os.IBinder;
8import android.os.RemoteException;
9import android.os.SystemProperties;
10import android.service.persistentdata.IPersistentDataBlockService;
11import android.util.Log;
12import com.android.internal.R;
13
14import java.io.DataInputStream;
15import java.io.DataOutputStream;
16import java.io.File;
17import java.io.FileInputStream;
18import java.io.FileNotFoundException;
19import java.io.FileOutputStream;
20import java.io.IOException;
21import java.nio.ByteBuffer;
22import java.nio.channels.FileChannel;
23
24/**
25 * Service for reading and writing blocks to a persistent partition.
26 * This data will live across factory resets.
27 *
28 * Allows writing one block at a time. Namely, each time
29 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
30 * is called, it will overwite the data that was previously written on the block.
31 *
32 * Clients can query the size of the currently written block via
33 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
34 *
35 * Clients can any number of bytes from the currently written block up to its total size by invoking
36 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
37 */
38public class PersistentDataBlockService extends SystemService {
39    private static final String TAG = PersistentDataBlockService.class.getSimpleName();
40
41    private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
42    private static final int HEADER_SIZE = 8;
43    private static final int BLOCK_ID = 0x1990;
44
45    private final Context mContext;
46    private final String mDataBlockFile;
47    private long mBlockDeviceSize;
48
49    private final int mAllowedUid;
50
51    public PersistentDataBlockService(Context context) {
52        super(context);
53        mContext = context;
54        mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
55        mBlockDeviceSize = 0; // Load lazily
56        String allowedPackage = context.getResources()
57                .getString(R.string.config_persistentDataPackageName);
58        PackageManager pm = mContext.getPackageManager();
59        int allowedUid = -1;
60        try {
61            allowedUid = pm.getPackageUid(allowedPackage,
62                    Binder.getCallingUserHandle().getIdentifier());
63        } catch (PackageManager.NameNotFoundException e) {
64            // not expected
65            Log.e(TAG, "not able to find package " + allowedPackage, e);
66        }
67
68        mAllowedUid = allowedUid;
69    }
70
71    @Override
72    public void onStart() {
73        publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
74    }
75
76    private void enforceOemUnlockPermission() {
77        mContext.enforceCallingOrSelfPermission(
78                Manifest.permission.OEM_UNLOCK_STATE,
79                "Can't access OEM unlock state");
80    }
81
82    private void enforceUid(int callingUid) {
83        if (callingUid != mAllowedUid) {
84            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
85        }
86    }
87
88    private int getTotalDataSize(DataInputStream inputStream) throws IOException {
89        int totalDataSize;
90        int blockId = inputStream.readInt();
91        if (blockId == BLOCK_ID) {
92            totalDataSize = inputStream.readInt();
93        } else {
94            totalDataSize = 0;
95        }
96        return totalDataSize;
97    }
98
99    private long maybeReadBlockDeviceSize() {
100        synchronized (this) {
101            if (mBlockDeviceSize == 0) {
102                mBlockDeviceSize = getBlockDeviceSize(mDataBlockFile);
103            }
104        }
105
106        return mBlockDeviceSize;
107    }
108
109    private native long getBlockDeviceSize(String path);
110
111    private final IBinder mService = new IPersistentDataBlockService.Stub() {
112        @Override
113        public int write(byte[] data) throws RemoteException {
114            enforceUid(Binder.getCallingUid());
115
116            // Need to ensure we don't write over the last byte
117            if (data.length > maybeReadBlockDeviceSize() - HEADER_SIZE - 1) {
118                return -1;
119            }
120
121            DataOutputStream outputStream;
122            try {
123                outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
124            } catch (FileNotFoundException e) {
125                Log.e(TAG, "partition not available?", e);
126                return -1;
127            }
128
129            ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
130            headerAndData.putInt(BLOCK_ID);
131            headerAndData.putInt(data.length);
132            headerAndData.put(data);
133
134            try {
135                outputStream.write(headerAndData.array());
136                return data.length;
137            } catch (IOException e) {
138                Log.e(TAG, "failed writing to the persistent data block", e);
139                return -1;
140            } finally {
141                try {
142                    outputStream.close();
143                } catch (IOException e) {
144                    Log.e(TAG, "failed closing output stream", e);
145                }
146            }
147        }
148
149        @Override
150        public int read(byte[] data) {
151            enforceUid(Binder.getCallingUid());
152
153            DataInputStream inputStream;
154            try {
155                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
156            } catch (FileNotFoundException e) {
157                Log.e(TAG, "partition not available?", e);
158                return -1;
159            }
160
161            try {
162                int totalDataSize = getTotalDataSize(inputStream);
163                return inputStream.read(data, 0,
164                        (data.length > totalDataSize) ? totalDataSize : data.length);
165            } catch (IOException e) {
166                Log.e(TAG, "failed to read data", e);
167                return -1;
168            } finally {
169                try {
170                    inputStream.close();
171                } catch (IOException e) {
172                    Log.e(TAG, "failed to close OutputStream");
173                }
174            }
175        }
176
177        @Override
178        public void setOemUnlockEnabled(boolean enabled) {
179            enforceOemUnlockPermission();
180            FileOutputStream outputStream;
181            try {
182                outputStream = new FileOutputStream(new File(mDataBlockFile));
183            } catch (FileNotFoundException e) {
184                Log.e(TAG, "parition not available", e);
185                return;
186            }
187
188            try {
189                FileChannel channel = outputStream.getChannel();
190
191                channel.position(maybeReadBlockDeviceSize() - 1);
192
193                ByteBuffer data = ByteBuffer.allocate(1);
194                data.put(enabled ? (byte) 1 : (byte) 0);
195                data.flip();
196
197                channel.write(data);
198            } catch (IOException e) {
199                Log.e(TAG, "unable to access persistent partition", e);
200            } finally {
201                try {
202                    outputStream.close();
203                } catch (IOException e) {
204                    Log.e(TAG, "failed to close OutputStream");
205                }
206            }
207        }
208
209        @Override
210        public boolean getOemUnlockEnabled() {
211            enforceOemUnlockPermission();
212            DataInputStream inputStream;
213            try {
214                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
215            } catch (FileNotFoundException e) {
216                Log.e(TAG, "partition not available");
217                return false;
218            }
219
220            try {
221                inputStream.skip(maybeReadBlockDeviceSize() - 1);
222                return inputStream.readByte() != 0;
223            } catch (IOException e) {
224                Log.e(TAG, "unable to access persistent partition", e);
225                return false;
226            } finally {
227                try {
228                    inputStream.close();
229                } catch (IOException e) {
230                    Log.e(TAG, "failed to close OutputStream");
231                }
232            }
233        }
234
235        @Override
236        public int getDataBlockSize() {
237            enforceUid(Binder.getCallingUid());
238
239            DataInputStream inputStream;
240            try {
241                inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
242            } catch (FileNotFoundException e) {
243                Log.e(TAG, "partition not available");
244                return 0;
245            }
246
247            try {
248                return getTotalDataSize(inputStream);
249            } catch (IOException e) {
250                Log.e(TAG, "error reading data block size");
251                return 0;
252            } finally {
253                try {
254                    inputStream.close();
255                } catch (IOException e) {
256                    Log.e(TAG, "failed to close OutputStream");
257                }
258            }
259        }
260    };
261}
262