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.app.ActivityManager;
20import android.content.pm.FeatureInfo;
21import android.os.*;
22import android.os.Process;
23import android.util.ArrayMap;
24import android.util.ArraySet;
25import android.util.Slog;
26import android.util.SparseArray;
27import android.util.Xml;
28
29import libcore.io.IoUtils;
30
31import com.android.internal.util.XmlUtils;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35
36import java.io.File;
37import java.io.FileNotFoundException;
38import java.io.FileReader;
39import java.io.IOException;
40
41import static com.android.internal.util.ArrayUtils.appendInt;
42
43/**
44 * Loads global system configuration info.
45 */
46public class SystemConfig {
47    static final String TAG = "SystemConfig";
48
49    static SystemConfig sInstance;
50
51    // Group-ids that are given to all packages as read from etc/permissions/*.xml.
52    int[] mGlobalGids;
53
54    // These are the built-in uid -> permission mappings that were read from the
55    // system configuration files.
56    final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>();
57
58    // These are the built-in shared libraries that were read from the
59    // system configuration files.  Keys are the library names; strings are the
60    // paths to the libraries.
61    final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();
62
63    // These are the features this devices supports that were read from the
64    // system configuration files.
65    final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
66
67    // These are the features which this device doesn't support; the OEM
68    // partition uses these to opt-out of features from the system image.
69    final ArraySet<String> mUnavailableFeatures = new ArraySet<>();
70
71    public static final class PermissionEntry {
72        public final String name;
73        public int[] gids;
74
75        PermissionEntry(String _name) {
76            name = _name;
77        }
78    }
79
80    // These are the permission -> gid mappings that were read from the
81    // system configuration files.
82    final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
83
84    // These are the packages that are white-listed to be able to run in the
85    // background while in power save mode, as read from the configuration files.
86    final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
87
88    // These are the app package names that should not allow IME switching.
89    final ArraySet<String> mFixedImeApps = new ArraySet<>();
90
91    public static SystemConfig getInstance() {
92        synchronized (SystemConfig.class) {
93            if (sInstance == null) {
94                sInstance = new SystemConfig();
95            }
96            return sInstance;
97        }
98    }
99
100    public int[] getGlobalGids() {
101        return mGlobalGids;
102    }
103
104    public SparseArray<ArraySet<String>> getSystemPermissions() {
105        return mSystemPermissions;
106    }
107
108    public ArrayMap<String, String> getSharedLibraries() {
109        return mSharedLibraries;
110    }
111
112    public ArrayMap<String, FeatureInfo> getAvailableFeatures() {
113        return mAvailableFeatures;
114    }
115
116    public ArrayMap<String, PermissionEntry> getPermissions() {
117        return mPermissions;
118    }
119
120    public ArraySet<String> getAllowInPowerSave() {
121        return mAllowInPowerSave;
122    }
123
124    public ArraySet<String> getFixedImeApps() {
125        return mFixedImeApps;
126    }
127
128    SystemConfig() {
129        // Read configuration from system
130        readPermissions(Environment.buildPath(
131                Environment.getRootDirectory(), "etc", "sysconfig"), false);
132        // Read configuration from the old permissions dir
133        readPermissions(Environment.buildPath(
134                Environment.getRootDirectory(), "etc", "permissions"), false);
135        // Only read features from OEM config
136        readPermissions(Environment.buildPath(
137                Environment.getOemDirectory(), "etc", "sysconfig"), true);
138        readPermissions(Environment.buildPath(
139                Environment.getOemDirectory(), "etc", "permissions"), true);
140    }
141
142    void readPermissions(File libraryDir, boolean onlyFeatures) {
143        // Read permissions from given directory.
144        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
145            if (!onlyFeatures) {
146                Slog.w(TAG, "No directory " + libraryDir + ", skipping");
147            }
148            return;
149        }
150        if (!libraryDir.canRead()) {
151            Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
152            return;
153        }
154
155        // Iterate over the files in the directory and scan .xml files
156        File platformFile = null;
157        for (File f : libraryDir.listFiles()) {
158            // We'll read platform.xml last
159            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
160                platformFile = f;
161                continue;
162            }
163
164            if (!f.getPath().endsWith(".xml")) {
165                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
166                continue;
167            }
168            if (!f.canRead()) {
169                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
170                continue;
171            }
172
173            readPermissionsFromXml(f, onlyFeatures);
174        }
175
176        // Read platform permissions last so it will take precedence
177        if (platformFile != null) {
178            readPermissionsFromXml(platformFile, onlyFeatures);
179        }
180    }
181
182    private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
183        FileReader permReader = null;
184        try {
185            permReader = new FileReader(permFile);
186        } catch (FileNotFoundException e) {
187            Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
188            return;
189        }
190
191        final boolean lowRam = ActivityManager.isLowRamDeviceStatic();
192
193        try {
194            XmlPullParser parser = Xml.newPullParser();
195            parser.setInput(permReader);
196
197            int type;
198            while ((type=parser.next()) != parser.START_TAG
199                       && type != parser.END_DOCUMENT) {
200                ;
201            }
202
203            if (type != parser.START_TAG) {
204                throw new XmlPullParserException("No start tag found");
205            }
206
207            if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
208                throw new XmlPullParserException("Unexpected start tag in " + permFile
209                        + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
210            }
211
212            while (true) {
213                XmlUtils.nextElement(parser);
214                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
215                    break;
216                }
217
218                String name = parser.getName();
219                if ("group".equals(name) && !onlyFeatures) {
220                    String gidStr = parser.getAttributeValue(null, "gid");
221                    if (gidStr != null) {
222                        int gid = android.os.Process.getGidForName(gidStr);
223                        mGlobalGids = appendInt(mGlobalGids, gid);
224                    } else {
225                        Slog.w(TAG, "<group> without gid in " + permFile + " at "
226                                + parser.getPositionDescription());
227                    }
228
229                    XmlUtils.skipCurrentTag(parser);
230                    continue;
231                } else if ("permission".equals(name) && !onlyFeatures) {
232                    String perm = parser.getAttributeValue(null, "name");
233                    if (perm == null) {
234                        Slog.w(TAG, "<permission> without name in " + permFile + " at "
235                                + parser.getPositionDescription());
236                        XmlUtils.skipCurrentTag(parser);
237                        continue;
238                    }
239                    perm = perm.intern();
240                    readPermission(parser, perm);
241
242                } else if ("assign-permission".equals(name) && !onlyFeatures) {
243                    String perm = parser.getAttributeValue(null, "name");
244                    if (perm == null) {
245                        Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
246                                + parser.getPositionDescription());
247                        XmlUtils.skipCurrentTag(parser);
248                        continue;
249                    }
250                    String uidStr = parser.getAttributeValue(null, "uid");
251                    if (uidStr == null) {
252                        Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
253                                + parser.getPositionDescription());
254                        XmlUtils.skipCurrentTag(parser);
255                        continue;
256                    }
257                    int uid = Process.getUidForName(uidStr);
258                    if (uid < 0) {
259                        Slog.w(TAG, "<assign-permission> with unknown uid \""
260                                + uidStr + "  in " + permFile + " at "
261                                + parser.getPositionDescription());
262                        XmlUtils.skipCurrentTag(parser);
263                        continue;
264                    }
265                    perm = perm.intern();
266                    ArraySet<String> perms = mSystemPermissions.get(uid);
267                    if (perms == null) {
268                        perms = new ArraySet<String>();
269                        mSystemPermissions.put(uid, perms);
270                    }
271                    perms.add(perm);
272                    XmlUtils.skipCurrentTag(parser);
273
274                } else if ("library".equals(name) && !onlyFeatures) {
275                    String lname = parser.getAttributeValue(null, "name");
276                    String lfile = parser.getAttributeValue(null, "file");
277                    if (lname == null) {
278                        Slog.w(TAG, "<library> without name in " + permFile + " at "
279                                + parser.getPositionDescription());
280                    } else if (lfile == null) {
281                        Slog.w(TAG, "<library> without file in " + permFile + " at "
282                                + parser.getPositionDescription());
283                    } else {
284                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
285                        mSharedLibraries.put(lname, lfile);
286                    }
287                    XmlUtils.skipCurrentTag(parser);
288                    continue;
289
290                } else if ("feature".equals(name)) {
291                    String fname = parser.getAttributeValue(null, "name");
292                    boolean allowed;
293                    if (!lowRam) {
294                        allowed = true;
295                    } else {
296                        String notLowRam = parser.getAttributeValue(null, "notLowRam");
297                        allowed = !"true".equals(notLowRam);
298                    }
299                    if (fname == null) {
300                        Slog.w(TAG, "<feature> without name in " + permFile + " at "
301                                + parser.getPositionDescription());
302                    } else if (allowed) {
303                        //Log.i(TAG, "Got feature " + fname);
304                        FeatureInfo fi = new FeatureInfo();
305                        fi.name = fname;
306                        mAvailableFeatures.put(fname, fi);
307                    }
308                    XmlUtils.skipCurrentTag(parser);
309                    continue;
310
311                } else if ("unavailable-feature".equals(name)) {
312                    String fname = parser.getAttributeValue(null, "name");
313                    if (fname == null) {
314                        Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
315                                + parser.getPositionDescription());
316                    } else {
317                        mUnavailableFeatures.add(fname);
318                    }
319                    XmlUtils.skipCurrentTag(parser);
320                    continue;
321
322                } else if ("allow-in-power-save".equals(name) && !onlyFeatures) {
323                    String pkgname = parser.getAttributeValue(null, "package");
324                    if (pkgname == null) {
325                        Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
326                                + parser.getPositionDescription());
327                    } else {
328                        mAllowInPowerSave.add(pkgname);
329                    }
330                    XmlUtils.skipCurrentTag(parser);
331                    continue;
332
333                } else if ("fixed-ime-app".equals(name) && !onlyFeatures) {
334                    String pkgname = parser.getAttributeValue(null, "package");
335                    if (pkgname == null) {
336                        Slog.w(TAG, "<fixed-ime-app> without package in " + permFile + " at "
337                                + parser.getPositionDescription());
338                    } else {
339                        mFixedImeApps.add(pkgname);
340                    }
341                    XmlUtils.skipCurrentTag(parser);
342                    continue;
343
344                } else {
345                    XmlUtils.skipCurrentTag(parser);
346                    continue;
347                }
348            }
349        } catch (XmlPullParserException e) {
350            Slog.w(TAG, "Got exception parsing permissions.", e);
351        } catch (IOException e) {
352            Slog.w(TAG, "Got exception parsing permissions.", e);
353        } finally {
354            IoUtils.closeQuietly(permReader);
355        }
356
357        for (String fname : mUnavailableFeatures) {
358            if (mAvailableFeatures.remove(fname) != null) {
359                Slog.d(TAG, "Removed unavailable feature " + fname);
360            }
361        }
362    }
363
364    void readPermission(XmlPullParser parser, String name)
365            throws IOException, XmlPullParserException {
366
367        name = name.intern();
368
369        PermissionEntry perm = mPermissions.get(name);
370        if (perm == null) {
371            perm = new PermissionEntry(name);
372            mPermissions.put(name, perm);
373        }
374        int outerDepth = parser.getDepth();
375        int type;
376        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
377               && (type != XmlPullParser.END_TAG
378                       || parser.getDepth() > outerDepth)) {
379            if (type == XmlPullParser.END_TAG
380                    || type == XmlPullParser.TEXT) {
381                continue;
382            }
383
384            String tagName = parser.getName();
385            if ("group".equals(tagName)) {
386                String gidStr = parser.getAttributeValue(null, "gid");
387                if (gidStr != null) {
388                    int gid = Process.getGidForName(gidStr);
389                    perm.gids = appendInt(perm.gids, gid);
390                } else {
391                    Slog.w(TAG, "<group> without gid at "
392                            + parser.getPositionDescription());
393                }
394            }
395            XmlUtils.skipCurrentTag(parser);
396        }
397    }
398}
399