1 /*
2  * Copyright (C) 2018 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 
17 package com.android.server;
18 
19 import android.content.Context;
20 import android.os.Binder;
21 import android.service.runtime.DebugEntryProto;
22 import android.service.runtime.RuntimeServiceInfoProto;
23 import android.util.Slog;
24 import android.util.proto.ProtoOutputStream;
25 
26 import libcore.timezone.TimeZoneDataFiles;
27 import libcore.util.CoreLibraryDebug;
28 import libcore.util.DebugInfo;
29 
30 import com.android.internal.util.DumpUtils;
31 import com.android.timezone.distro.DistroException;
32 import com.android.timezone.distro.DistroVersion;
33 import com.android.timezone.distro.FileUtils;
34 import com.android.timezone.distro.TimeZoneDistro;
35 
36 import java.io.File;
37 import java.io.FileDescriptor;
38 import java.io.IOException;
39 import java.io.PrintWriter;
40 
41 /**
42  * This service exists only as a "dumpsys" target which reports information about the status of the
43  * runtime and related libraries.
44  */
45 public class RuntimeService extends Binder {
46 
47     private static final String TAG = "RuntimeService";
48 
49     private final Context mContext;
50 
RuntimeService(Context context)51     public RuntimeService(Context context) {
52         mContext = context;
53     }
54 
55     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)56     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
57         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
58             return;
59         }
60 
61         boolean protoFormat = hasOption(args, "--proto");
62         ProtoOutputStream proto = null;
63 
64         DebugInfo coreLibraryDebugInfo = CoreLibraryDebug.getDebugInfo();
65         addTimeZoneApkDebugInfo(coreLibraryDebugInfo);
66 
67         if (protoFormat) {
68             proto = new ProtoOutputStream(fd);
69             reportTimeZoneInfoProto(coreLibraryDebugInfo, proto);
70         } else {
71             reportTimeZoneInfo(coreLibraryDebugInfo, pw);
72         }
73 
74         if (protoFormat) {
75             proto.flush();
76         }
77     }
78 
79     /** Returns {@code true} if {@code args} contains {@code arg}. */
hasOption(String[] args, String arg)80     private static boolean hasOption(String[] args, String arg) {
81         for (String opt : args) {
82             if (arg.equals(opt)) {
83                 return true;
84             }
85         }
86         return false;
87     }
88 
89     /**
90      * Add information to {@link DebugInfo} about the time zone data supplied by the
91      * "Time zone updates via APK" feature.
92      */
addTimeZoneApkDebugInfo(DebugInfo coreLibraryDebugInfo)93     private static void addTimeZoneApkDebugInfo(DebugInfo coreLibraryDebugInfo) {
94         // Add /data tz data set using the DistroVersion class (which libcore cannot use).
95         // This update mechanism will be removed after the time zone APEX is launched so this
96         // untidiness will disappear with it.
97         String debugKeyPrefix = "core_library.timezone.source.data_";
98         String versionFileName = TimeZoneDataFiles.getDataTimeZoneFile(
99                 TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
100         addDistroVersionDebugInfo(versionFileName, debugKeyPrefix, coreLibraryDebugInfo);
101     }
102 
103     /**
104      * Prints {@code coreLibraryDebugInfo} to {@code pw}.
105      *
106      * <p>If you change this method, make sure to modify
107      * {@link #reportTimeZoneInfoProto(DebugInfo, ProtoOutputStream)} as well.
108      */
reportTimeZoneInfo(DebugInfo coreLibraryDebugInfo, PrintWriter pw)109     private static void reportTimeZoneInfo(DebugInfo coreLibraryDebugInfo,
110             PrintWriter pw) {
111         pw.println("Core Library Debug Info: ");
112         for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) {
113             pw.print(debugEntry.getKey());
114             pw.print(": \"");
115             pw.print(debugEntry.getStringValue());
116             pw.println("\"");
117         }
118     }
119 
120     /**
121      * Adds {@code coreLibraryDebugInfo} to {@code protoStream}.
122      *
123      * <p>If you change this method, make sure to modify
124      * {@link #reportTimeZoneInfo(DebugInfo, PrintWriter)}.
125      */
reportTimeZoneInfoProto( DebugInfo coreLibraryDebugInfo, ProtoOutputStream protoStream)126     private static void reportTimeZoneInfoProto(
127             DebugInfo coreLibraryDebugInfo, ProtoOutputStream protoStream) {
128         for (DebugInfo.DebugEntry debugEntry : coreLibraryDebugInfo.getDebugEntries()) {
129             long entryToken = protoStream.start(RuntimeServiceInfoProto.DEBUG_ENTRY);
130             protoStream.write(DebugEntryProto.KEY, debugEntry.getKey());
131             protoStream.write(DebugEntryProto.STRING_VALUE, debugEntry.getStringValue());
132             protoStream.end(entryToken);
133         }
134     }
135 
136     /**
137      * Adds version information to {@code debugInfo} from the distro_version file that may exist
138      * at {@code distroVersionFileName}. If the file does not exist or cannot be read this is
139      * reported as debug information too.
140      */
addDistroVersionDebugInfo(String distroVersionFileName, String debugKeyPrefix, DebugInfo debugInfo)141     private static void addDistroVersionDebugInfo(String distroVersionFileName,
142             String debugKeyPrefix, DebugInfo debugInfo) {
143         File file = new File(distroVersionFileName);
144         String statusKey = debugKeyPrefix + "status";
145         if (file.exists()) {
146             try {
147                 byte[] versionBytes =
148                         FileUtils.readBytes(file, DistroVersion.DISTRO_VERSION_FILE_LENGTH);
149                 DistroVersion distroVersion = DistroVersion.fromBytes(versionBytes);
150                 String formatVersionString = distroVersion.formatMajorVersion + "."
151                         + distroVersion.formatMinorVersion;
152                 debugInfo.addStringEntry(statusKey, "OK")
153                         .addStringEntry(debugKeyPrefix + "formatVersion", formatVersionString)
154                         .addStringEntry(debugKeyPrefix + "rulesVersion",
155                                 distroVersion.rulesVersion)
156                         .addStringEntry(debugKeyPrefix + "revision",
157                                 distroVersion.revision);
158             } catch (IOException | DistroException e) {
159                 debugInfo.addStringEntry(statusKey, "ERROR");
160                 debugInfo.addStringEntry(debugKeyPrefix + "exception_class",
161                         e.getClass().getName());
162                 debugInfo.addStringEntry(debugKeyPrefix + "exception_msg", e.getMessage());
163                 logMessage("Error reading " + file, e);
164             }
165         } else {
166             debugInfo.addStringEntry(statusKey, "NOT_FOUND");
167         }
168     }
169 
logMessage(String msg, Throwable t)170     private static void logMessage(String msg, Throwable t) {
171         Slog.v(TAG, msg, t);
172     }
173 }
174