“This is the first day of my participation in the First Challenge 2022. For details: First Challenge 2022.”
Analytical tool analysis of system startup process analysis
In Android 11.0 system start source, before we have analyzed, in the system start, call SecondStageMain function to start Zygote and system services, and in this process, there is a very important link is to parse the configuration file, and to parse the configuration file, the first thing to build parsing tools.
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
// 1. Create a parsing tool
Parser parser = CreateParser(action_manager, service_list);
// 2. Obtain the corresponding parsing file
std::string bootscript = GetProperty("ro.boot.init_rc"."");
// If ro.boot.init_rc is undefined, the specified parse file is directly parsed
if (bootscript.empty()) {
parser.ParseConfig("/system/etc/init/hw/init.rc");
if(! parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
// late_import is available only in Q and earlier release. As we don't
// have system_ext in those versions, skip late_import for system_ext.
parser.ParseConfig("/system_ext/etc/init");
if(! parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
if(! parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if(! parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init"); }}else {
// If the ro.boot.init_rc property is defined, the configuration file it points to is parsed directlyparser.ParseConfig(bootscript); }}Copy the code
The LoadBootScripts function is used to parse the configuration file. The CreateParser function is used to create three different types of parsers.
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
parser.AddSectionParser("service".std::make_unique<ServiceParser>(
&service_list, GetSubcontext(), std::nullopt));
parser.AddSectionParser("on".std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
parser.AddSectionParser("import".std::make_unique<ImportParser>(&parser));
return parser;
}
Copy the code
The CreateParser function has two parameters, the ActionManager object and the ServiceList object. The initialization of these two objects is described in the previous section. This function returns a Parser object, which we’ll examine later.
First, three parsers are added to the Parser object using the AddSectionParser function.
std::map<std::string, std::unique_ptr<SectionParser>> section_parsers_;
void Parser::AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser) {
section_parsers_[name] = std::move(parser);
}
Copy the code
The Parser object defines a map named section_parsers_, and then adds three parsers to the map. The corresponding keys are “service”, “on”, and “import”. Both inherit from the SectionParser class, which is very simple to define.
system/core/init/parser.h
class SectionParser {
public:
virtual ~SectionParser() {}
virtual Result<void> ParseSection(std::vector<std::string>&& args, const std::string& filename,
int line) = 0;
virtual Result<void> ParseLineSection(std::vector<std::string>&&, int) { return {}; };
virtual Result<void> EndSection() { return {}; };
virtual void EndFile(){};
};
Copy the code
Next, let’s take a look at each of the three parsing tools
Entry to the Parser: the Parser
Initialize the
Parser::Parser() {}
Start parsing
Parser.parseconfig (bootscript) parser.parseconfig (bootscript) parser.parseconfig (bootscript) parser.parseconfig (bootscript) parser.parseconfig (bootscript) parser.parseconfig (bootscript) parser.parseconfig (bootscript) parser.parseconfig (bootscript) Here, because we need to parse/system/core/rootdir/init zygote32. Rc file, so the bootscript to/system/core/rootdir/init zygote32. Rc string
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) {
return ParseConfigDir(path);
}
return ParseConfigFile(path);
}
Copy the code
Check whether the current passed path is a folder. If it is a folder, then call ParseConfigDir to parse each configuration file in the folder. Here we passed a file, so ParseConfigFile
bool Parser::ParseConfigFile(const std::string& path) {
LOG(INFO) << "Parsing file " << path << "...";
android::base::Timer t;
// 1. Read all the content in the configuration file from the file and save it as a string
auto config_contents = ReadFile(path);
if(! config_contents.ok()) { LOG(INFO) <<"Unable to read config file '" << path << "'." << config_contents.error();
return false;
}
// 2. Call ParseData to parse all the configuration contents in the above configuration file
ParseData(path, &config_contents.value());
LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".) ";
return true;
}
Copy the code
From the code above, there are two steps
Read all the content in the configuration file from the file and save it as a string. This part is not analyzed here
Second, the ParseData function is called to parse the contents of the corresponding configuration file
void Parser::ParseData(const std::string& filename, std::string* data) {
// 1. Add the terminator to the end of the parsed data
data->push_back('\n');
data->push_back('\ 0');
// Parse the status data
parse_state state;
state.line = 0;
state.ptr = data->data();
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
int section_start_line = -1;
std::vector<std::string> args;
// If we encounter a bad section start, there is no valid parser object to parse the subsequent
// sections, so we must suppress errors until the next valid section is found.
bool bad_section_found = false;
2. Define an end_section function
// The function closes the configuration file after it has been parsed
auto end_section = [&] {
bad_section_found = false;
if (section_parser == nullptr) return;
if(auto result = section_parser->EndSection(); ! result.ok()) { parse_error_count_++; LOG(ERROR) << filename <<":" << section_start_line << ":" << result.error();
}
section_parser = nullptr;
section_start_line = -1;
};
3.Infinite loop, parsing configuration datafor (;;) {
switch (next_token(&state)) {
// The configuration file is parsed
case T_EOF:
end_section();
for (const auto& [section_name, section_parser] : section_parsers_) {
section_parser->EndFile();
}
return;
// The configuration file parses a new row of data
case T_NEWLINE: {
// Verify the parsed line
state.line++;
if (args.empty()) break;
// If we have a line matching a prefix we recognize, call its callback and unset any
// current section parsers. This is meant for /sys/ and /dev/ line entries for
// uevent.
// Find the corresponding substring starting with args[0] from the string
auto line_callback = std::find_if(
line_callbacks_.begin(), line_callbacks_.end(),
[&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });
if(line_callback ! = line_callbacks_.end()) {// end_section
end_section();
if(auto result = line_callback->second(std::move(args)); ! result.ok()) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line << ":"<< result.error(); }}else if (section_parsers_.count(args[0])) {
end_section();
// Get the parsing tool
section_parser = section_parsers_[args[0]].get();
section_start_line = state.line;
/ / Section
if(auto result = section_parser->ParseSection(std::move(args), filename, state.line); ! result.ok()) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line << ":" << result.error();
section_parser = nullptr;
bad_section_found = true;
}
// The Section Parser tool is found in the library
} else if (section_parser) {
// Call the ParseLineSection of the parsing tool to parse the corresponding row
if(auto result = section_parser->ParseLineSection(std::move(args), state.line); ! result.ok()) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line << ":" << result.error();
}
// else
} else if(! bad_section_found) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line
<< ": Invalid section keyword found";
}
args.clear();
break;
}
// If it is TEXT, add it as an argument
case T_TEXT:
args.emplace_back(state.text);
break; }}}Copy the code
As you can see from the above code, here
First, add an end character to the corresponding configuration data to prevent access to other data in memory
Second, the configuration file data is manipulated using the parse_state structure
struct parse_state
{
char *ptr; // The PTR of state here points to the first character of configuration file data
char *text; // Default data
int line; // Default is 0
int nexttoken; // Default is 0
};
Copy the code
Third, we define a reference to the end_section function. The main purpose of this function is to call the EndSection function if section_parser is not empty and do some closing parsing.
Finally, the configuration file data is parsed bit by bit through a loop
Next, let’s start parsing the corresponding configuration file data
Inside the loop, is a switch statement whose body is the next_token function and whose argument is referenced by the parse_state object defined above
int next_token(struct parse_state *state)
{
char *x = state->ptr;
char *s;
if (state->nexttoken) {
int t = state->nexttoken;
state->nexttoken = 0;
return t;
}
for (;;) {
switch (*x) {
case 0:
state->ptr = x;
return T_EOF;
case '\n':
x++;
state->ptr = x;
return T_NEWLINE;
case ' ':
case '\t':
case '\r':
x++;
continue;
case The '#':
while(*x && (*x ! ='\n')) x++;
if (*x == '\n') {
state->ptr = x+1;
return T_NEWLINE;
} else {
state->ptr = x;
return T_EOF;
}
default: goto text; }}textdone:
state->ptr = x;
*s = 0;
return T_TEXT;
text:
state->text = s = x;
textresume:
for (;;) {
switch (*x) {
case 0:
goto textdone;
case ' ':
case '\t':
case '\r':
x++;
goto textdone;
case '\n':
state->nexttoken = T_NEWLINE;
x++;
goto textdone;
case '"':
x++;
for (;;) {
switch (*x) {
case 0:
/* unterminated quoted thing */
state->ptr = x;
return T_EOF;
case '"':
x++;
goto textresume;
default: *s++ = *x++; }}break;
case '\ \':
x++;
switch (*x) {
case 0:
goto textdone;
case 'n':
*s++ = '\n';
x++;
break;
case 'r':
*s++ = '\r';
x++;
break;
case 't':
*s++ = '\t';
x++;
break;
case '\ \':
*s++ = '\ \';
x++;
break;
case '\r':
/* \ <cr> <lf> -> line continuation */
if (x[1] != '\n') {
x++;
continue;
}
x++;
FALLTHROUGH_INTENDED;
case '\n':
/* \ <lf> -> line continuation */
state->line++;
x++;
/* eat any extra whitespace */
while((*x == ' ') || (*x == '\t')) x++;
continue;
default:
/* unknown escape -- just copy */
*s++ = *x++;
}
continue;
default: *s++ = *x++; }}return T_EOF;
}
Copy the code
As you can see, the next_token function does the following
If the current state-> PTR refers to an uppercase or lowercase character, the character is saved to point to the next character and parsed until a non-alphabetic character is parsed. Finally, all parsed letters are referred to the head with state->text, and T_TEXT is returned
If it is currently newline ‘\n’, it indicates that the previous line has been parsed, points state-> PTR to the current position, and returns T_NEWLINE
If the current is a space, ‘\t’, ‘\r’, etc., parse the next character
If the current character is’ # ‘, the line is filtered. Of course, if the last character parsed is’ \n ‘, T_NEWLINE is returned. If not, the configuration file data has been parsed, T_EOF is returned
So, back to ParseData’s for loop
When the next_token function returns T_EOF, the configuration file has been parsed, and the endsections of all parsing tools (here are the three added by AddSectionParser) are called to end parsing
When the next_token function returns T_NEWLINE, it fetches all the data in the corresponding ARGS. Where does the data in the ARGS come from? Look at the following parsing
When the next_token function returns T_TEXT, the corresponding returned string is added to the ARGS string array
If ParseData is parsed to a string, it is added to the args string array. When parsed to a string, it is parsed to T_NEWLINE. If parsed to a configuration data, it is parsed to T_EOF
Next, let’s take a look at the specific parsing work of the following three parsing tools.
ServiceParser Parsing tool
Initialize the
From the above code, the initialization statement for ServiceParser is as follows
std::make_unique<ServiceParser>(&service_list, GetSubcontext(), std::nullopt)
The ServiceParser constructor is as follows
ServiceParser(ServiceList* service_list, Subcontext* subcontext,
const std::optional<InterfaceInheritanceHierarchyMap>& interface_inheritance_hierarchy,
bool from_apex = false)
: service_list_(service_list),
subcontext_(subcontext),
interface_inheritance_hierarchy_(interface_inheritance_hierarchy),
service_(nullptr),
from_apex_(from_apex) {}
Copy the code
That is, the ServiceParser object
The reference to a ServiceList object named Service_list_ points to the ServiceList object you just created,
The Subcontext object named subContext_ refers to the previous Subcontext object,
The name is interface_inheritance_hierarchy_ nullopt,
The Service object named Service_ points to nullPtr
From_apex_ is set to false
Analytical process
If you need a configuration file to work with the ServiceParser, we’ll use the classic Zygote configuration file. Here we use init.zygote32.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority- 20user root
group root readproc reserved_disk
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
task_profiles ProcessCapacityHigh
critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
Copy the code
The ParseData function of Parser shows that the configuration file is parsed line by line, so all the contents of the first line are read first, then the string is added to args when it is encountered, then the line is parsed and processed in T_NEWLINE, so we parsed line by line
The first line
Let’s look at the first line
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
Copy the code
After the above analysis, all strings will be added to the ARGS string array because they are initially composed of letters (including ‘/’ characters and ‘-‘)
That is, when the last ‘\n’ is encountered, the data in args is as follows
{" service ", "zygote", "/ system/bin/app_process", "- Xzygote", "/ system/bin", "zygote", "- start - system - server"}Copy the code
Next comes the newline character, which returns T_NEWLINE
case T_NEWLINE: {
// Verify the number of parsed rows
state.line++;
// If args is empty, no parsing is required
if (args.empty()) break;
// If we have a line matching a prefix we recognize, call its callback and unset any
// current section parsers. This is meant for /sys/ and /dev/ line entries for
// uevent.
// Find the corresponding substring starting with args[0] from the string
// Line_callbacks are defined as line_callbacks
auto line_callback = std::find_if(
line_callbacks_.begin(), line_callbacks_.end(),
[&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });
if(line_callback ! = line_callbacks_.end()) {// Close the section parsing tool
end_section();
if(auto result = line_callback->second(std::move(args)); ! result.ok()) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line << ":" << result.error();
}
// Use the args[0] string to determine whether the map contains the parsing tool for args[0]
} else if (section_parsers_.count(args[0])) {
end_section();
// Get the parsing tool
section_parser = section_parsers_[args[0]].get();
section_start_line = state.line;
/ / Section
if(auto result = section_parser->ParseSection(std::move(args), filename, state.line); ! result.ok()) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line << ":" << result.error();
section_parser = nullptr;
bad_section_found = true;
}
// If the current args[0] does not find the corresponding parsing tool, then check whether the parsing tool already exists
} else if (section_parser) {
// Call the ParseLineSection of the parsing tool to parse the corresponding row
if(auto result = section_parser->ParseLineSection(std::move(args), state.line); ! result.ok()) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line << ":" << result.error();
}
// If none of the preceding conditions is met, logs cannot be parsed for the current row
} else if(! bad_section_found) { parse_error_count_++; LOG(ERROR) << filename <<":" << state.line
<< ": Invalid section keyword found";
}
// Finally empty the args for the next line to parse
args.clear();
break;
}
Copy the code
If args[0] is a string of “service”, section_parser_ map corresponds to ServiceParser, so section_parser is set to ServiceParser. The tool’s ParseSection function is then called
Result<void> ServiceParser::ParseSection(std::vector<std::string>&& args,
const std::string& filename, int line) {
// The number of arGs is 7, which does not meet the condition
if (args.size() < 3) {
return Error() < <"services must have a name and a program";
}
Zygote = zygote
const std::string& name = args[1];
// Check whether the name is valid
if(! IsValidName(name)) {return Error() < <"invalid service name '" << name << "'";
}
// Set the file path
filename_ = filename;
Subcontext* restart_action_subcontext = nullptr;
// Check whether the configuration file is resolved under /vendor or/ODM
if (subcontext_ && subcontext_->PathMatchesSubcontext(filename)) {
restart_action_subcontext = subcontext_;
}
// Get the last five parameters of args
std::vector<std::string> str_args(args.begin() + 2, args.end()); .// Initialize a Service object named service_
service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args, from_apex_);
return {};
}
Copy the code
As you can see from the above code, this code basically creates a Service object and initializes service_ to point to the object reference
Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
const std::vector<std::string>& args, bool from_apex)
: Service(name, 0.0.0, {}, 0."", subcontext_for_restart_commands, args, from_apex) {}
Service::Service(const std::string& name, unsigned flags, uid_t uid, gid_t gid,
const std::vector<gid_t>& supp_gids, int namespace_flags,
const std::string& seclabel, Subcontext* subcontext_for_restart_commands,
const std::vector<std::string>& args, bool from_apex)
: name_(name),
classnames_({"default"}),
flags_(flags),
pid_(0),
crash_count_(0),
proc_attr_{.ioprio_class = IoSchedClass_NONE,
.ioprio_pri = 0,
.uid = uid,
.gid = gid,
.supp_gids = supp_gids,
.priority = 0},
namespaces_{.flags = namespace_flags},
seclabel_(seclabel),
subcontext_(subcontext_for_restart_commands),
onrestart_(false, subcontext_for_restart_commands, "<Service '" + name + "' onrestart>".0."onrestart", {}),
oom_score_adjust_(DEFAULT_OOM_SCORE_ADJUST),
start_order_(0),
args_(args),
from_apex_(from_apex) {}
Copy the code
According to its constructor, the main set here is its name_ to zygote and args_ to the last five parameter arrays read above, i.e
{"/system/bin/app_process ", "- Xzygote", "/ system/bin", "zygote", "- start - system - server"}Copy the code
The second line
Next, parse the second line
class main
Copy the code
From the above analysis, you can see that args contains two string arguments, “class” and “main”
T_NEWLINE is displayed. Since there is no parser for the class and Section_Parser is set to ServiceParser, call ParseLineSection of ServiceParser
Result<void> ServiceParser::ParseLineSection(std::vector<std::string>&& args, int line) {
// The previous line was created when it was parsed
if(! service_) {return {};
}
// Get the parsing tool from GetParserMap (corresponding parsing method)
auto parser = GetParserMap().Find(args);
if(! parser.ok())return parser.error();
// When the corresponding parsing method is obtained, call it
return std::invoke(*parser, this.std::move(args));
}
Copy the code
Analyze the code above, get the corresponding parsing method and call it
const KeywordMap<ServiceParser::OptionParser>& ServiceParser::GetParserMap() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
// clang-format off
static const KeywordMap<ServiceParser::OptionParser> parser_map = {
{"capabilities", {0, kMax, &ServiceParser::ParseCapabilities}},
{"class", {1, kMax, &ServiceParser::ParseClass}},
{"console", {0.1, &ServiceParser::ParseConsole}},
{"critical", {0.2, &ServiceParser::ParseCritical}},
{"disabled", {0.0, &ServiceParser::ParseDisabled}},
{"enter_namespace", {2.2, &ServiceParser::ParseEnterNamespace}},
{"file", {2.2, &ServiceParser::ParseFile}},
{"group", {1, NR_SVC_SUPP_GIDS + 1, &ServiceParser::ParseGroup}},
{"interface", {2.2, &ServiceParser::ParseInterface}},
{"ioprio", {2.2, &ServiceParser::ParseIoprio}},
{"keycodes", {1, kMax, &ServiceParser::ParseKeycodes}},
{"memcg.limit_in_bytes", {1.1, &ServiceParser::ParseMemcgLimitInBytes}},
{"memcg.limit_percent", {1.1, &ServiceParser::ParseMemcgLimitPercent}},
{"memcg.limit_property", {1.1, &ServiceParser::ParseMemcgLimitProperty}},
{"memcg.soft_limit_in_bytes",
{1.1, &ServiceParser::ParseMemcgSoftLimitInBytes}},
{"memcg.swappiness", {1.1, &ServiceParser::ParseMemcgSwappiness}},
{"namespace", {1.2, &ServiceParser::ParseNamespace}},
{"oneshot", {0.0, &ServiceParser::ParseOneshot}},
{"onrestart", {1, kMax, &ServiceParser::ParseOnrestart}},
{"oom_score_adjust", {1.1, &ServiceParser::ParseOomScoreAdjust}},
{"override", {0.0, &ServiceParser::ParseOverride}},
{"priority", {1.1, &ServiceParser::ParsePriority}},
{"reboot_on_failure", {1.1, &ServiceParser::ParseRebootOnFailure}},
{"restart_period", {1.1, &ServiceParser::ParseRestartPeriod}},
{"rlimit", {3.3, &ServiceParser::ParseProcessRlimit}},
{"seclabel", {1.1, &ServiceParser::ParseSeclabel}},
{"setenv", {2.2, &ServiceParser::ParseSetenv}},
{"shutdown", {1.1, &ServiceParser::ParseShutdown}},
{"sigstop", {0.0, &ServiceParser::ParseSigstop}},
{"socket", {3.6, &ServiceParser::ParseSocket}},
{"stdio_to_kmsg", {0.0, &ServiceParser::ParseStdioToKmsg}},
{"task_profiles", {1, kMax, &ServiceParser::ParseTaskProfiles}},
{"timeout_period", {1.1, &ServiceParser::ParseTimeoutPeriod}},
{"updatable", {0.0, &ServiceParser::ParseUpdatable}},
{"user", {1.1, &ServiceParser::ParseUser}},
{"writepid", {1, kMax, &ServiceParser::ParseWritepid}},
};
// clang-format on
return parser_map;
}
Copy the code
As you can see, the class string corresponding analytic function for ServiceParser: : ParseClass
Result<void> ServiceParser::ParseClass(std::vector<std::string>&& args) {
service_->classnames_ = std::set<std::string>(args.begin() + 1, args.end());
return {};
}
Copy the code
Set the classNames_ field referenced by Service_ to the second args parameter, main
The third line
And then the third row
priority -20
Copy the code
Based on the above analysis, the call ServiceParser: : ParsePriority function reference
Result<void> ServiceParser::ParsePriority(std::vector<std::string>&& args) {
service_->proc_attr_.priority = 0;
if(! ParseInt(args[1], &service_->proc_attr_.priority,
static_cast<int>(ANDROID_PRIORITY_HIGHEST), // highest is negative
static_cast<int>(ANDROID_PRIORITY_LOWEST))) {
return Errorf("process priority value must be range {} - {}", ANDROID_PRIORITY_HIGHEST,
ANDROID_PRIORITY_LOWEST);
}
return {};
}
Copy the code
Set service_->proc_attr_. Priority to -20, indicating the highest priority
In the fourth row
The following
user root
Copy the code
Based on the above analysis, call the &ServiceParser::ParseUser function
Result<void> ServiceParser::ParseUser(std::vector<std::string>&& args) {
auto uid = DecodeUid(args[1]);
if(! uid.ok()) {return Error() < <"Unable to find UID for '" << args[1] < <"'." << uid.error();
}
service_->proc_attr_.uid = *uid;
return {};
}
Copy the code
Set service_->proc_attr_. Uid
The fifth row
The following
group root readproc reserved_disk
Copy the code
According to the previous analysis, this call ServiceParser: : ParseGroup
Result<void> ServiceParser::ParseGroup(std::vector<std::string>&& args) {
/ / gid
auto gid = DecodeUid(args[1]);
if(! gid.ok()) {return Error() < <"Unable to decode GID for '" << args[1] < <"'." << gid.error();
}
/ / Settings service_ - > proc_attr_ gid
service_->proc_attr_.gid = *gid;
for (std::size_t n = 2; n < args.size(); n++) {
gid = DecodeUid(args[n]);
if(! gid.ok()) {return Error() < <"Unable to decode GID for '" << args[n] << "'." << gid.error();
}
// Parse the corresponding service_->proc_attr_. Supp_gids
service_->proc_attr_.supp_gids.emplace_back(*gid);
}
return {};
}
Copy the code
As shown above, resolve the corresponding service_->proc_attr_. Gid and service_->proc_attr_. Supp_gids
Lines six through seven
The following
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
Copy the code
The same socket at the beginning of fields, call ServiceParser: : ParseSocket
Result<void> ServiceParser::ParseSocket(std::vector<std::string>&& args) {
SocketDescriptor socket;
// Set the socket name
socket.name = std::move(args[1]);
// Set the socket type
auto types = Split(args[2]."+");
if (types[0] = ="stream") {
socket.type = SOCK_STREAM;
} else if (types[0] = ="dgram") {
socket.type = SOCK_DGRAM;
} else if (types[0] = ="seqpacket") {
socket.type = SOCK_SEQPACKET;
} else {
return Error() < <"socket type must be 'dgram', 'stream' or 'seqpacket', got '" << types[0] < <"' instead.";
}
if (types.size() > 1) {
if (types.size() == 2 && types[1] = ="passcred") {
socket.passcred = true;
} else {
return Error() < <"Only 'passcred' may be used to modify the socket type";
}
}
errno = 0;
char* end = nullptr;
// socket.perm
socket.perm = strtol(args[3].c_str(), &end, 8);
if(errno ! =0) {
return ErrnoError() << "Unable to parse permissions '" << args[3] < <"'";
}
if (end == args[3].c_str() || *end ! ='\ 0') {
errno = EINVAL;
return ErrnoError() << "Unable to parse permissions '" << args[3] < <"'";
}
if (args.size() > 4) {
/ / parsing socket. The uid
auto uid = DecodeUid(args[4]);
if(! uid.ok()) {return Error() < <"Unable to find UID for '" << args[4] < <"'." << uid.error();
}
socket.uid = *uid;
}
if (args.size() > 5) {
/ / parsing socket. Gid
auto gid = DecodeUid(args[5]);
if(! gid.ok()) {return Error() < <"Unable to find GID for '" << args[5] < <"'." << gid.error();
}
socket.gid = *gid;
}
// There is no seventh parameter
socket.context = args.size() > 6 ? args[6] : "";
auto old = std::find_if(service_->sockets_.begin(), service_->sockets_.end(),
[&socket](const auto& other) { return socket.name == other.name; });
if(old ! = service_->sockets_.end()) {return Error() < <"duplicate socket descriptor '" << socket.name << "'";
}
service_->sockets_.emplace_back(std::move(socket));
return {};
}
Copy the code
Parsing service_ – > sockets_
Lines eight to fourteen
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
Copy the code
By the foregoing can cause, call ServiceParser: : ParseOnrestart
Result<void> ServiceParser::ParseOnrestart(std::vector<std::string>&& args) {
args.erase(args.begin());
int line = service_->onrestart_.NumCommands() + 1;
if(auto result = service_->onrestart_.AddCommand(std::move(args), line); ! result.ok()) {return Error() < <"cannot add Onrestart command: " << result.error();
}
return {};
}
Copy the code
Add the command line to service_-> onRestart_, where onrestart_ is an Action object
Result<void> Action::AddCommand(std::vector<std::string>&& args, int line) {
if(! function_map_) {return Error() < <"no function map available";
}
auto map_result = function_map_->Find(args);
if(! map_result.ok()) {return Error() << map_result.error();
}
commands_.emplace_back(map_result->function.map_result->run_in_subcontext.std: :move(args),
line);
return {};
}
Copy the code
That is, add the corresponding action function to service_->onrestart_->commands_, which will be examined later
Line 15
task_profiles ProcessCapacityHigh
Copy the code
Similarly, call ServiceParser: : ParseTaskProfiles
Result<void> ServiceParser::ParseTaskProfiles(std::vector<std::string>&& args) {
args.erase(args.begin());
service_->task_profiles_ = std::move(args);
return {};
}
Copy the code
Set the service_->task_profiles_ parameter
Line 16
critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
Copy the code
Call ServiceParser: : ParseCritical
Result<void> ServiceParser::ParseCritical(std::vector<std::string>&& args) {
std::optional<std::string> fatal_reboot_target;
std::optional<std::chrono::minutes> fatal_crash_window;
for (auto it = args.begin() + 1; it ! = args.end(); ++it) { auto arg = android::base::Split(*it,"=");
if(arg.size() ! =2) {
return Error() < <"critical: Argument '" << *it << "' is not supported";
// The second argument is target
} else if (arg[0] = ="target") {
// Set fatal_reboot_target to zygote-fatal
fatal_reboot_target = arg[1];
Zygote. Critical_window. minute = off and set the property value
} else if (arg[0] = ="window") {
int minutes;
auto window = ExpandProps(arg[1]);
if (!window.ok()) {
return Error() < <"critical: Could not expand argument ': " << arg[1];
}
// Return directly here
if (*window= ="off") {
return {};
}
if(! ParseInt(*window, &minutes, 0)) {
return Error() < <"critical: 'fatal_crash_window' must be an integer > 0";
}
fatal_crash_window = std::chrono::minutes(minutes);
} else {
return Error() < <"critical: Argument '" << *it << "' is not supported"; }}if (fatal_reboot_target) {
service_->fatal_reboot_target_ = *fatal_reboot_target;
}
if (fatal_crash_window) {
service_->fatal_crash_window_ = *fatal_crash_window;
}
service_->flags_ |= SVC_CRITICAL;
return {};
}
Copy the code
Set service_->fatal_reboot_target_ to “zygote-fatal”
The end of the
The call will return a T_EOF, which will call ServiceParser’s EndSection function (since ServiceParser doesn’t define EndFile, no need to analyze).
Result<void> ServiceParser::EndSection(){...// Check whether the corresponding Service object is already included in the list
Service* old_service = service_list_->FindService(service_->name());
if (old_service) {
......
}
// Save the newly created Service object into the ServiceManager with AddService
service_list_->AddService(std::move(service_));
return {};
}
Copy the code
As you can see, the Service object you just created will eventually be saved to the ServiceManager by AddService
void ServiceList::AddService(std::unique_ptr<Service> service) {
services_.emplace_back(std::move(service));
}
Copy the code
It is stored in the Service_ array of the Service
conclusion
ServiceParser parses the configuration file into a Service object and adds it to the ServiceManager for management
ActionParser tool
Initialize the
Based on the foregoing, ActionParser is initialized as follows
Note that the corresponding ActionParser field in the map is “on”.
std::make_unique<ActionParser>(&action_manager, GetSubcontext())
Copy the code
Corresponding ActionParser constructor
ActionParser(ActionManager* action_manager, Subcontext* subcontext)
: action_manager_(action_manager), subcontext_(subcontext), action_(nullptr) {}
Copy the code
As you can see, this is mainly about assigning values to parameters in the ActionParser object
Analytical process
Rc configuration file. Check the init.usb.rc configuration file. Here we only look at the previous data parsing
on post-fs-data
chown system system /sys/class/android_usb/android0/f_mass_storage/lun/file
chmod 0660 /sys/class/android_usb/android0/f_mass_storage/lun/file
chown system system /sys/class/android_usb/android0/f_rndis/ethaddr
chmod 0660 /sys/class/android_usb/android0/f_rndis/ethaddr
mkdir /data/misc/adb 02750 system shell
mkdir /data/adb 0700 root root encryption=Require
# adbd is controlled via property triggers in init.<platform>.usb.rc
service adbd /system/bin/adbd --root_seclabel=u:r:su:s0
class core
socket adbd seqpacket 660 system system
disabled
updatable
seclabel u:r:adbd:s0
on property:vendor.sys.usb.adb.disabled=*
setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
# Set default value on sys.usb.configfs early in boot sequence. It will be
# overridden in `on boot` action of init.hardware.rc.
on init
setprop sys.usb.configfs 0.Copy the code
According to previous analysis, Parser parses line by line in the process of parsing, and stores the corresponding string into args string array when encountering letters and characters
The first line
on post-fs-data
Copy the code
According to the previous analysis, when the Parser is parsed to this line, the above two strings are put into the args string array respectively, and then the first string “on” is used to find the Parser object here and call its ParseSection function
Result<void> ActionParser::ParseSection(std::vector<std::string>&& args,
const std::string& filename, int line) {
// Place the ARgs data into the triggers string array. There is only one element in triggers, post-fs-data, that is resolved here
std::vector<std::string> triggers(args.begin() + 1, args.end());
if (triggers.size() < 1) {
return Error() < <"Actions must have a trigger";
}
Subcontext* action_subcontext = nullptr;
// Check whether the current file address exists in /vendor or /odm, and action_subcontext is nullptr
if (subcontext_ && subcontext_->PathMatchesSubcontext(filename)) {
action_subcontext = subcontext_;
}
std::string event_trigger;
std::map<std::string, std::string> property_triggers;
// Invoke the ParseTriggers function
if(auto result = ParseTriggers(triggers, action_subcontext, &event_trigger, &property_triggers); ! result.ok()) {return Error() < <"ParseTriggers() failed: " << result.error();
}
// Initialize an Action object
auto action = std::make_unique<Action>(false, action_subcontext, filename, line, event_trigger,
property_triggers);
// Finally set action_ to the Action you just created
action_ = std::move(action);
return {};
}
Copy the code
As you can see from the code above, over here
The ParseTriggers function of ActionParser is called first
Result<void> ParseTriggers(const std::vector<std::string>& args, Subcontext* subcontext,
std::string* event_trigger,
std::map<std::string, std::string>* property_triggers) {
const static std::string prop_str("property:");
// The for loop looks at each passed argument
for (std::size_t i = 0; i < args.size(); ++i) {
// Check whether it is null
if (args[i].empty()) {
return Error() < <"empty trigger is not valid";
}
// Check whether there are two arguments
if (i % 2) {
if(args[i] ! ="&") {
return Error() < <"&& is the only symbol allowed to concatenate actions";
} else {
continue; }}// Check whether the parameter is property: the data at the beginning
if(! args[i].compare(0, prop_str.length(), prop_str)) {
if(auto result = ParsePropertyTrigger(args[i], subcontext, property_triggers); ! result.ok()) {returnresult; }}else {
if(! event_trigger->empty()) {return Error() < <"multiple event triggers are not allowed";
}
// Determine if the passed parameter is a valid event trigger
if(auto result = ValidateEventTrigger(args[i]); ! result.ok()) {return result;
}
// Save the event trigger*event_trigger = args[i]; }}return {};
}
Copy the code
In this case, the main thing is to make event_trigger point to the data represented by args[0], i.e., post-fs-data
Next, we initialize an Action object
Action::Action(bool oneshot, Subcontext* subcontext, const std::string& filename, int line,
const std::string& event_trigger,
const std::map<std::string, std::string>& property_triggers)
: property_triggers_(property_triggers),
event_trigger_(event_trigger),
oneshot_(oneshot),
subcontext_(subcontext),
filename_(filename),
line_(line) {}
Copy the code
From the perspective of the parameters passed in, we assign values to the Action parameters
Oneshot_ is assigned to false
Event_trigger_ assigns the address of the string just passed in, and its value is post-Fs-data
Subcontext_ assigns to the Subcontext object initialized in the previous article
Filename_ is the address of the configuration file
Line_ indicates what line this is
The second line
chown system system /sys/class/android_usb/android0/f_mass_storage/lun/file
Copy the code
The section_parser object is an ActionParser object, so call the ParseLineSection function of ActionParser. Its arguments are the strings described above
Result<void> ActionParser::ParseLineSection(std::vector<std::string>&& args, int line) {
return action_ ? action_->AddCommand(std::move(args), line) : Result<void> {}; }Copy the code
Action_ we just created, so call it AddCommand
Result<void> Action::AddCommand(std::vector<std::string>&& args, int line) {
if(! function_map_) {return Error() < <"no function map available";
}
// Find the function corresponding to the parameter in function_map_
auto map_result = function_map_->Find(args);
// If no corresponding parsing function is found, exit
if(! map_result.ok()) {return Error() << map_result.error();
}
// Store the corresponding functions and parameters in the corresponding command line array
commands_.emplace_back(map_result->function.map_result->run_in_subcontext.std: :move(args),
line);
return {};
}
Copy the code
What is function_map? This is set during system startup via Action::set_function_map, which we’ll revisit
// Builtin-function-map start
const BuiltinFunctionMap& GetBuiltinFunctionMap() {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
// clang-format off
static const BuiltinFunctionMap builtin_functions = {
{"bootchart", {1.1, {false, do_bootchart}}},
{"chmod", {2.2, {true, do_chmod}}},
{"chown", {2.3, {true, do_chown}}},
{"class_reset", {1.1, {false, do_class_reset}}},
{"class_reset_post_data", {1.1, {false, do_class_reset_post_data}}},
{"class_restart", {1.1, {false, do_class_restart}}},
{"class_start", {1.1, {false, do_class_start}}},
{"class_start_post_data", {1.1, {false, do_class_start_post_data}}},
{"class_stop", {1.1, {false, do_class_stop}}},
{"copy", {2.2, {true, do_copy}}},
{"copy_per_line", {2.2, {true, do_copy_per_line}}},
{"domainname", {1.1, {true, do_domainname}}},
{"enable", {1.1, {false, do_enable}}},
{"exec", {1, kMax, {false, do_exec}}},
{"exec_background", {1, kMax, {false, do_exec_background}}},
{"exec_start", {1.1, {false, do_exec_start}}},
{"export", {2.2, {false, do_export}}},
{"hostname", {1.1, {true, do_hostname}}},
{"ifup", {1.1, {true, do_ifup}}},
{"init_user0", {0.0, {false, do_init_user0}}},
{"insmod", {1, kMax, {true, do_insmod}}},
{"installkey", {1.1, {false, do_installkey}}},
{"interface_restart", {1.1, {false, do_interface_restart}}},
{"interface_start", {1.1, {false, do_interface_start}}},
{"interface_stop", {1.1, {false, do_interface_stop}}},
{"load_exports", {1.1, {false, do_load_exports}}},
{"load_persist_props", {0.0, {false, do_load_persist_props}}},
{"load_system_props", {0.0, {false, do_load_system_props}}},
{"loglevel", {1.1, {false, do_loglevel}}},
{"mark_post_data", {0.0, {false, do_mark_post_data}}},
{"mkdir", {1.6, {true, do_mkdir}}},
// TODO: Do mount operations in vendor_init.
// mount_all is currently too complex to run in vendor_init as it queues action triggers,
// imports rc scripts, etc. It should be simplified and run in vendor_init context.
// mount and umount are run in the same context as mount_all for symmetry.
{"mount_all", {0, kMax, {false, do_mount_all}}},
{"mount", {3, kMax, {false, do_mount}}},
{"perform_apex_config", {0.0, {false, do_perform_apex_config}}},
{"umount", {1.1, {false, do_umount}}},
{"umount_all", {0.1, {false, do_umount_all}}},
{"update_linker_config", {0.0, {false, do_update_linker_config}}},
{"readahead", {1.2, {true, do_readahead}}},
{"remount_userdata", {0.0, {false, do_remount_userdata}}},
{"restart", {1.1, {false, do_restart}}},
{"restorecon", {1, kMax, {true, do_restorecon}}},
{"restorecon_recursive", {1, kMax, {true, do_restorecon_recursive}}},
{"rm", {1.1, {true, do_rm}}},
{"rmdir", {1.1, {true, do_rmdir}}},
{"setprop", {2.2, {true, do_setprop}}},
{"setrlimit", {3.3, {false, do_setrlimit}}},
{"start", {1.1, {false, do_start}}},
{"stop", {1.1, {false, do_stop}}},
{"swapon_all", {0.1, {false, do_swapon_all}}},
{"enter_default_mount_ns", {0.0, {false, do_enter_default_mount_ns}}},
{"symlink", {2.2, {true, do_symlink}}},
{"sysclktz", {1.1, {false, do_sysclktz}}},
{"trigger", {1.1, {false, do_trigger}}},
{"verity_update_state", {0.0, {false, do_verity_update_state}}},
{"wait", {1.2, {true, do_wait}}},
{"wait_for_prop", {2.2, {false, do_wait_for_prop}}},
{"write", {2.2, {true, do_write}}},
};
// clang-format on
return builtin_functions;
}
Copy the code
From this map, we can find the corresponding chown function is do_chown function, we do not do too much parsing here, according to the name, this is to change the file permissions
Lines three to seven
As you can see from the parsing of the second line above, commands are added to the Commands_ command line array of Aciton objects
Lines 10 to 15
Based on ServiceParser parsing, a Service object is created and added to the ServiceManager for management
However, since ActionParser has not encountered T_EOF before, how is the corresponding Action object saved?
So this is using the end_section function that we’ve looked at before, which we’ve looked at before, mainly when we run into T_EOF or when we run into the next SectionParser, and in this function, This is done by calling the corresponding section_parser EndSection function. For init.usb.rc, ActionParser::EndSection will be called until the next ServiceParser is encountered
Result<void> ActionParser::EndSection() {
if (action_ && action_->NumCommands() > 0) {
action_manager_->AddAction(std::move(action_));
}
return {};
}
Copy the code
That is, the Action object created in the first line is added to the ActionManager for management
void ActionManager::AddAction(std::unique_ptr<Action> action) {
actions_.emplace_back(std::move(action));
}
Copy the code
As you can see, the Action object is added to the Action_ parameter array of the ActionManager
Lines 17 through 18
on property:vendor.sys.usb.adb.disabled=*
setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
Copy the code
The difference here from the Action parsing on the first line is in ParserTriggers, where the ParsePropertyTrigger function is called because the args being parsed starts with property:
Result<void> ParsePropertyTrigger(const std::string& trigger, Subcontext* subcontext,
std::map<std::string, std::string>* property_triggers) {
const static std::string prop_str("property:");
// Get proterty: the following string
std::string prop_name(trigger.substr(prop_str.length()));
// Check whether the string contains the '=' character
size_t equal_pos = prop_name.find('=');
if (equal_pos == std::string::npos) {
return Error() < <"property trigger found without matching '='";
}
/ / name resolution attribute value for the vendor. The sys. Usb. The adb. The disabled, the attribute value is *
std::string prop_value(prop_name.substr(equal_pos + 1));
prop_name.erase(equal_pos);
// Since the attribute value name begins with vendor, the function returns true here
if(! IsActionableProperty(subcontext, prop_name)) {return Error() < <"unexported property trigger found: " << prop_name;
}
// Save the paired properties into property_triggers
if(auto [it, inserted] = property_triggers->emplace(prop_name, prop_value); ! inserted) {return Error() < <"multiple property triggers found for same property";
}
return {};
}
Copy the code
The Action object is then created to indicate that the Action item is triggered as a property, and the next line is parsed to add the command line for the Action item, and finally, as above, to the Action_action item array of the ActionManager
conclusion
Firing the ActionParser tool parses an Action object that contains multiple commands and is eventually added to the ActionManager
ImportParser Parsing tool
Initialize the
std::make_unique<ImportParser>(&parser)
Copy the code
Initialize an ImportParser object directly
ImportParser(Parser* parser) : parser_(parser) {}
Copy the code
Analytical process
Let’s look at this using init.rc
import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
Copy the code
In the first line of parsing, the ImportParser ParserSection function is triggered
Result<void> ImportParser::ParseSection(std::vector<std::string>&& args,
const std::string& filename, int line) {
if(args.size() ! =2) {
return Error() < <"single argument needed for import\n";
}
// Parse the second argument
auto conf_file = ExpandProps(args[1]);
if(! conf_file.ok()) {return Error() < <"Could not expand import: " << conf_file.error();
}
LOG(INFO) << "Added '" << *conf_file << "' to import list";
if (filename_.empty()) filename_ = filename;
// Add to imports_
imports_.emplace_back(std::move(*conf_file), line);
return {};
}
Copy the code
The first parameter is parsed
Result<std::string> ExpandProps(const std::string& src) {
const char* src_ptr = src.c_str();
std::string dst;
/* - variables can either be $x.y or ${x.y}, in case they are only part * of the string. * - will accept $$ as a literal $. * - no nested property expansion, i.e. ${foo.${bar}} is not supported, * bad things will happen * - ${x.y:-default} will return default value if property empty. */
while (*src_ptr) {
const char* c;
c = strchr(src_ptr, '$');
if(! c) { dst.append(src_ptr);return dst;
}
dst.append(src_ptr, c);
c++;
if (*c == '$') {
dst.push_back(*(c++));
src_ptr = c;
continue;
} else if (*c == '\ 0') {
return dst;
}
std::string prop_name;
std::string def_val;
if (*c == '{') {
c++;
const char* end = strchr(c, '} ');
if(! end) {// failed to find closing brace, abort.
return Error() < <"unexpected end of string in '" << src << "', looking for }";
}
prop_name = std::string(c, end);
c = end + 1;
size_t def = prop_name.find(: "-");
if (def < prop_name.size()) {
def_val = prop_name.substr(def + 2);
prop_name = prop_name.substr(0, def); }}else {
prop_name = c;
if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_R__) {
return Error() < <"using deprecated syntax for specifying property '" << c
<< "', use ${name} instead";
} else {
LOG(ERROR) << "using deprecated syntax for specifying property '" << c
<< "', use ${name} instead";
}
c += prop_name.size();
}
if (prop_name.empty()) {
return Error() < <"invalid zero-length property name in '" << src << "'";
}
std::string prop_val = android::base::GetProperty(prop_name, "");
if (prop_val.empty()) {
if (def_val.empty()) {
return Error() < <"property '" << prop_name << "' doesn't exist while expanding '"
<< src << "'";
}
prop_val = def_val;
}
dst.append(prop_val);
src_ptr = c;
}
return dst;
}
Copy the code
The value of the property name contained in the address of the second parameter file is parsed and replaced into the string. After that, all configuration data starting with import is saved into the import_ array
So where does the parsing take place? Remember when the configuration file is parsed, T_EOF is reported to the Parser? The EndFile function of all configuration file parsing tools is called
void ImportParser::EndFile() {
auto current_imports = std::move(imports_);
imports_.clear();
for (const auto& [import, line_num] : current_imports) {
parser_->ParseConfig(import); }}Copy the code
Then parses all imports_ containing configuration file addresses using ParseConfig of Parser
In init.rc, the import line is added at the beginning of the file, but it is not parsed until the configuration file is parsed
conclusion
ImportParser is a parsing tool that adds and imports other parsing files and parses them together
Summary of parsing tools
From the analysis of the three parsing tools above, we know that at the end of parsing the configuration files, the configuration files are changed to
- The Service object is added to the Service_ Service array of the ServiceList for unified management
- The Action object, added to the ActionManager actions_ Action items array for unified management, also contains command lines stored in the Commands_ array
- The ImportParser utility introduces a new configuration file