android 에서 property 값 변화시마다 임의의 동작 수행 방밥
init.rc에 on property 사용
아래와 같은 코드가 있음
on property:persist.service.adb.enable=1
start adbd
설명 : persist.service.adb.enable=1 값이 write 되면 adbd가 실행됨
좀 더 자세한 설명
https://android.googlesource.com/platform/system/core/+/refs/tags/android-9.0.0_r45/init/
Triggers
Triggers are strings which can be used to match certain kinds of events and used to cause an action to occur.
Triggers are subdivided into event triggers and property triggers.
Event triggers are strings triggered by the ‘trigger’ command or by the QueueEventTrigger() function within the init executable. These take the form of a simple string such as ‘boot’ or ‘late-init’.
Property triggers are strings triggered when a named property changes value to a given new value or when a named property changes value to any new value. These take the form of ‘property:=’ and ‘property:=*’ respectively. Property triggers are additionally evaluated and triggered accordingly during the initial boot phase of init.
An Action can have multiple property triggers but may only have one event trigger.
For example:
on boot && property:a=b
defines an action that is only executed when the ‘boot’ event trigger happens and the property a equals b.on property:a=b && property:c=d
defines an action that is executed at three times:- During initial boot if property a=b and property c=d.
- Any time that property a transitions to value b, while property c already equals d.
- Any time that property c transitions to value d, while property a already equals b.
만약 값이 변하게 될때마다 처리를 하고 싶을때는 어떻게 할까?
on property:persist.service.adb.enable=*
property 서비스의 전반적인 흐름은 다음과 같습니다.
아래함수에서 init 서비스에서 property 서비스가 만들어지고 소켓 polling을 하고 있다가 커맨드가 도착하면 동작하는 방식입니다.
840void start_property_service() { 841 selinux_callback cb; 842 cb.func_audit = SelinuxAuditCallback; 843 selinux_set_callback(SELINUX_CB_AUDIT, cb); 844 845 property_set("ro.property_service.version", "2"); 846 847 property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 848 false, 0666, 0, 0, nullptr); 849 if (property_set_fd == -1) { 850 PLOG(FATAL) << "start_property_service socket creation failed"; 851 } 852 853 listen(property_set_fd, 8); 854 855 register_epoll_handler(property_set_fd, handle_property_set_fd); 856}
130static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) { 131 size_t valuelen = value.size(); 132 133 if (!IsLegalPropertyName(name)) { 134 *error = "Illegal property name"; 135 return PROP_ERROR_INVALID_NAME; 136 } 137 138 if (valuelen >= PROP_VALUE_MAX && !StartsWith(name, "ro.")) { 139 *error = "Property value too long"; 140 return PROP_ERROR_INVALID_VALUE; 141 } 142 143 if (mbstowcs(nullptr, value.data(), 0) == static_cast<std::size_t>(-1)) { 144 *error = "Value is not a UTF8 encoded string"; 145 return PROP_ERROR_INVALID_VALUE; 146 } 147 148 prop_info* pi = (prop_info*) __system_property_find(name.c_str()); 149 if (pi != nullptr) { 150 // ro.* properties are actually "write-once". 151 if (StartsWith(name, "ro.")) { 152 *error = "Read-only property was already set"; 153 return PROP_ERROR_READ_ONLY_PROPERTY; 154 } 155 156 __system_property_update(pi, value.c_str(), valuelen); 157 } else { 158 int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen); 159 if (rc < 0) { 160 *error = "__system_property_add failed"; 161 return PROP_ERROR_SET_FAILED; 162 } 163 } 164 165 // Don't write properties to disk until after we have read all default 166 // properties to prevent them from being overwritten by default values. 167 if (persistent_properties_loaded && StartsWith(name, "persist.")) { 168 WritePersistentProperty(name, value); 169 } 170 property_changed(name, value); 171 return PROP_SUCCESS; 172}
165void property_changed(const std::string& name, const std::string& value) { 166 // If the property is sys.powerctl, we bypass the event queue and immediately handle it. 167 // This is to ensure that init will always and immediately shutdown/reboot, regardless of 168 // if there are other pending events to process or if init is waiting on an exec service or 169 // waiting on a property. 170 // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific 171 // commands to be executed. 172 if (name == "sys.powerctl") { 173 // Despite the above comment, we can't call HandlePowerctlMessage() in this function, 174 // because it modifies the contents of the action queue, which can cause the action queue 175 // to get into a bad state if this function is called from a command being executed by the 176 // action queue. Instead we set this flag and ensure that shutdown happens before the next 177 // command is run in the main init loop. 178 // TODO: once property service is removed from init, this will never happen from a builtin, 179 // but rather from a callback from the property service socket, in which case this hack can 180 // go away. 181 shutdown_command = value; 182 do_shutdown = true; 183 } 184 185 if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value); 186
최종단에서는 property 값이 변할때 trigger 할것이 있는지 검사하게 되는 CheckPropertyTriggers 함수를 이용하게됩니다.
관련소스
trigger_value != "*" 조건이 false가 되므로 found 가 true가 됩니다.
155bool Action::CheckPropertyTriggers(const std::string& name, 156 const std::string& value) const { 157 if (property_triggers_.empty()) { 158 return true; 159 } 160 161 bool found = name.empty(); 162 for (const auto& [trigger_name, trigger_value] : property_triggers_) { 163 if (trigger_name == name) { 164 if (trigger_value != "*" && trigger_value != value) { 165 return false; 166 } else { 167 found = true; 168 } 169 } else { 170 std::string prop_val = android::base::GetProperty(trigger_name, ""); 171 if (prop_val.empty() || (trigger_value != "*" && trigger_value != prop_val)) { 172 return false; 173 } 174 } 175 } 176 return found; 177}
예제)
이미 찾아보니 * 로 사용하는 소스들이 존재 합니다.
722# system server cannot write to /proc/sys files, 723# and chown/chmod does not work for /proc/sys/ entries. 724# So proxy writes through init. 725on property:sys.sysctl.extra_free_kbytes=* 726 write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes} 727 728# "tcp_default_init_rwnd" Is too long! 729on property:sys.sysctl.tcp_def_init_rwnd=* 730 write /proc/sys/net/ipv4/tcp_default_init_rwnd ${sys.sysctl.tcp_def_init_rwnd}
추가로 write시 ${sys.sysctl.extra_free_kbytes} 이부분이 있는데 이건 뭘까 검색해 봤습니다.
설명상은 <contents>에 의해 확장될수 있다는 의미로 보입니다.
534`write <path> <content>` 535> Open the file at _path_ and write a string to it with write(2). 536 If the file does not exist, it will be created. If it does exist, 537 it will be truncated. Properties are expanded within _content_. 538
그리고 이것은 ${property.name} 이런 형태로 표현할 수 있다는것이고요.
15System properties can be expanded using the syntax 16`${property.name}`. This also works in contexts where concatenation is 17required, such as `import /init.recovery.${ro.hardware}.rc`.
1082 {"write", {2, 2, {true, do_write}}},
724static Result<Success> do_write(const BuiltinArguments& args) { 725 if (auto result = WriteFile(args[1], args[2]); !result) { 726 return Error() << "Unable to write to file '" << args[1] << "': " << result.error(); 727 } 728 729 return Success(); 730}
204Result<Success> WriteFile(const std::string& path, const std::string& content) { 205 android::base::unique_fd fd(TEMP_FAILURE_RETRY( 206 OpenFile(path, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0600))); 207 if (fd == -1) { 208 return ErrnoError() << "open() failed"; 209 } 210 if (!android::base::WriteStringToFd(content, fd)) { 211 return ErrnoError() << "Unable to write file contents"; 212 } 213 return Success(); 214}
81bool WriteStringToFd(const std::string& content, int fd) { 82 const char* p = content.data(); 83 size_t left = content.size(); 84 while (left > 0) { 85 ssize_t n = TEMP_FAILURE_RETRY(write(fd, p, left)); 86 if (n == -1) { 87 return false; 88 } 89 p += n; 90 left -= n; 91 } 92 return true; 93}
따라가 봤지만 ${} 확장하는 코드는 보이지 않습니다. 그렇다면 do_write 하기전에 이미 확장된것은 아닌지 검토해 보았습니다.
36Result<Success> RunBuiltinFunction(const BuiltinFunction& function, 37 const std::vector<std::string>& args, 38 const std::string& context) { 39 auto builtin_arguments = BuiltinArguments(context); 40 41 builtin_arguments.args.resize(args.size()); 42 builtin_arguments.args[0] = args[0]; 43 for (std::size_t i = 1; i < args.size(); ++i) { 44 if (!expand_props(args[i], &builtin_arguments.args[i])) { 45 return Error() << "cannot expand '" << args[i] << "'"; 46 } 47 } 48 49 return function(builtin_arguments); 50}
실제 확장은 expand_props 여기에서 이루어지며 인자를 넘기기 전에 이루어 집니다.
296bool expand_props(const std::string& src, std::string* dst) { 297 const char* src_ptr = src.c_str(); 298 299 if (!dst) { 300 return false; 301 } 302 303 /* - variables can either be $x.y or ${x.y}, in case they are only part 304 * of the string. 305 * - will accept $$ as a literal $. 306 * - no nested property expansion, i.e. ${foo.${bar}} is not supported, 307 * bad things will happen 308 * - ${x.y:-default} will return default value if property empty. 309 */ 310 while (*src_ptr) { 311 const char* c; 312 313 c = strchr(src_ptr, '$'); 314 if (!c) { 315 dst->append(src_ptr); 316 return true; 317 } 318 319 dst->append(src_ptr, c); 320 c++; 321 322 if (*c == '$') { 323 dst->push_back(*(c++)); 324 src_ptr = c; 325 continue; 326 } else if (*c == '\0') { 327 return true; 328 } 329 330 std::string prop_name; 331 std::string def_val; 332 if (*c == '{') { 333 c++; 334 const char* end = strchr(c, '}'); 335 if (!end) { 336 // failed to find closing brace, abort. 337 LOG(ERROR) << "unexpected end of string in '" << src << "', looking for }"; 338 return false; 339 } 340 prop_name = std::string(c, end); 341 c = end + 1; 342 size_t def = prop_name.find(":-"); 343 if (def < prop_name.size()) { 344 def_val = prop_name.substr(def + 2); 345 prop_name = prop_name.substr(0, def); 346 } 347 } else { 348 prop_name = c; 349 LOG(ERROR) << "using deprecated syntax for specifying property '" << c << "', use ${name} instead"; 350 c += prop_name.size(); 351 } 352 353 if (prop_name.empty()) { 354 LOG(ERROR) << "invalid zero-length property name in '" << src << "'"; 355 return false; 356 } 357 358 std::string prop_val = android::base::GetProperty(prop_name, ""); 359 if (prop_val.empty()) { 360 if (def_val.empty()) { 361 LOG(ERROR) << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'"; 362 return false; 363 } 364 prop_val = def_val; 365 } 366 367 dst->append(prop_val); 368 src_ptr = c; 369 } 370 371 return true; 372}
댓글 없음:
댓글 쓰기