目录
概述
使用伪终端的方式来处理子进程的log输出,logwrapper会等待子进程执行完毕之后再退出
源码解析
1. logwrap模块
1.1 logwrap_fork_execvp
// {"/system/bin/vdc","cryptfs", "encryptFstab", attempted_entry.blk_device, attempted_entry.mount_point}
// logwrap_fork_execvp(argv.size(), argv.data(), ret, false, LOG_ALOG, false, nullptr); ret为null
int logwrap_fork_execvp(int argc, const char* const* argv, int* status, bool forward_signals,
int log_target, bool abbreviated, const char* file_path) {
pid_t pid;
int parent_ptty;
sigset_t oldset;
int rc = 0;
rc = pthread_mutex_lock(&fd_mutex);
if (rc) {
ERROR("failed to lock signal_fd mutex\n");
goto err_lock;
}
// 打开一个伪终端设备
/* Use ptty instead of socketpair so that STDOUT is not buffered */
parent_ptty = TEMP_FAILURE_RETRY(posix_openpt(O_RDWR | O_CLOEXEC));
if (parent_ptty < 0) {
ERROR("Cannot create parent ptty\n");
rc = -1;
goto err_open;
}
char child_devname[64];
// grantpt:改变伪终端的mode和owner,授予对从属伪终端的访问权限
// unlockpt:解锁一个伪终端主从对
// ptsname_r:获取从伪终端的名字
if (grantpt(parent_ptty) || unlockpt(parent_ptty) ||
ptsname_r(parent_ptty, child_devname, sizeof(child_devname)) != 0) {
ERROR("Problem with /dev/ptmx\n");
rc = -1;
goto err_ptty;
}
// forward_signals为false,不走这里
if (forward_signals) {
// Block these signals until we have the child pid and our signal handlers set up.
block_signals(&oldset);
}
// 创建子进程
pid = fork();
if (pid < 0) {
ERROR("Failed to fork\n");
rc = -1;
goto err_fork;
} else if (pid == 0) {// 子进程
pthread_mutex_unlock(&fd_mutex);
if (forward_signals) {
unblock_signals(&oldset);
}
// 创建会话,并设置进程组id
setsid();
// 所以,子进程和伪终端的slave相连接
int child_ptty = TEMP_FAILURE_RETRY(open(child_devname, O_RDWR | O_CLOEXEC));
if (child_ptty < 0) {
FATAL_CHILD("Cannot open child_ptty: %s\n", strerror(errno));
}
close(parent_ptty);
// 设置标准输出和错误为伪终端
// 只设置了错误和输出,表示是log输出信息
dup2(child_ptty, 1);
dup2(child_ptty, 2);
close(child_ptty);
// 子进程函数
child(argc, argv);
} else {// 父进程
if (forward_signals) {
setup_signal_handlers(pid);
unblock_signals(&oldset);
}
// 父进程函数,
rc = parent(argv[0], parent_ptty, pid, status, log_target, abbreviated, file_path,
forward_signals);
if (forward_signals) {
restore_signal_handlers();
}
}
err_fork:
if (forward_signals) {
unblock_signals(&oldset);
}
err_ptty:
close(parent_ptty);
err_open:
pthread_mutex_unlock(&fd_mutex);
err_lock:
return rc;
}
1.2 child-直接执行execvp函数
static void child(int argc, const char* const* argv) {
// create null terminated argv_child array
char* argv_child[argc + 1];
memcpy(argv_child, argv, argc * sizeof(char*));
argv_child[argc] = nullptr;
// 执行程序
if (execvp(argv_child[0], argv_child)) {
FATAL_CHILD("executing %s failed: %s\n", argv_child[0], strerror(errno));
}
}
1.3 parent-处理子进程的log输出
// parent(argv[0], parent_ptty, pid, status, log_target, abbreviated, file_path, forward_signals);
// vdc,伪终端,子进程pid,status是null,LOG_ALOG,abbreviated是false,file_path是null,forward_signals是false
static int parent(const char* tag, int parent_read, pid_t pid, int* chld_sts, int log_target,
bool abbreviated, const char* file_path, bool forward_signals) {
int status = 0;
char buffer[4096];
struct pollfd poll_fds[] = {
{// 伪终端master设备的fd
.fd = parent_read,
.events = POLLIN,
},
};
int rc = 0;
int fd;
struct log_info log_info;
int a = 0; // start index of unprocessed data
int b = 0; // end index of unprocessed data
int sz;
bool found_child = false;
// There is a very small chance that opening child_ptty in the child will fail, but in this case
// POLLHUP will not be generated below. Therefore, we use a 1 second timeout for poll() until
// we receive a message from child_ptty. If this times out, we call waitpid() with WNOHANG to
// check the status of the child process and exit appropriately if it has terminated.
bool received_messages = false;
char tmpbuf[256];
// tag为命令名字vdc(/system/bin/vdc取basename vdc)
log_info.btag = basename(tag);
if (!log_info.btag) {
log_info.btag = tag;
}
// 这里为false
if (abbreviated && (log_target == LOG_NONE)) {
abbreviated = 0;
}
if (abbreviated) {
init_abbr_buf(&log_info.a_buf);
}
// 这是将log打印到串口上
if (log_target & LOG_KLOG) {
snprintf(log_info.klog_fmt, sizeof(log_info.klog_fmt), "<6>%.*s: %%s\n", MAX_KLOG_TAG,
log_info.btag);
}
// 这个file_path是将log打印到文件中
if ((log_target & LOG_FILE) && !file_path) {
/* No file_path specified, clear the LOG_FILE bit */
log_target &= ~LOG_FILE;
}
if (log_target & LOG_FILE) {
fd = open(file_path, O_WRONLY | O_CREAT | O_CLOEXEC, 0664);
if (fd < 0) {
ERROR("Cannot log to file %s\n", file_path);
log_target &= ~LOG_FILE;
} else {
lseek(fd, 0, SEEK_END);
log_info.fp = fdopen(fd, "a");
}
}
// log_target为LOG_ALOG
log_info.log_target = log_target;
log_info.abbreviated = abbreviated;
// 第一次为false
while (!found_child) {
// 第一次为false
int timeout = received_messages ? -1 : 1000;
// 等待子进程往slave设备写东西,然后父进程就可以从master设备读东西了
if (TEMP_FAILURE_RETRY(poll(poll_fds, arraysize(poll_fds), timeout)) < 0) {
ERROR("poll failed\n");
rc = -1;
goto err_poll;
}
// 从master设备读东西
if (poll_fds[0].revents & POLLIN) {
received_messages = true;
sz = TEMP_FAILURE_RETRY(read(parent_read, &buffer[b], sizeof(buffer) - 1 - b));
sz += b;
// Log one line at a time
for (b = 0; b < sz; b++) {
if (buffer[b] == ‘\r‘) {
if (abbreviated) {
/* The abbreviated logging code uses newline as
* the line separator. Lucikly, the pty layer
* helpfully cooks the output of the command
* being run and inserts a CR before NL. So
* I just change it to NL here when doing
* abbreviated logging.
*/
buffer[b] = ‘\n‘;
} else {
buffer[b] = ‘\0‘;
}
} else if (buffer[b] == ‘\n‘) {
buffer[b] = ‘\0‘;
// 打印log信息
log_line(&log_info, &buffer[a], b - a);
a = b + 1;
}
}
if (a == 0 && b == sizeof(buffer) - 1) {
// buffer is full, flush
buffer[b] = ‘\0‘;
log_line(&log_info, &buffer[a], b - a);
b = 0;
} else if (a != b) {
// Keep left-overs
b -= a;
memmove(buffer, &buffer[a], b);
a = 0;
} else {
a = 0;
b = 0;
}
}
if (!received_messages || (poll_fds[0].revents & POLLHUP)) {
int ret;
sigset_t oldset;
if (forward_signals) {
// Our signal handlers forward these signals to ‘child_pid‘, but waitpid() may reap
// the child, so we must block these signals until we either 1) conclude that the
// child is still running or 2) determine the child has been reaped and we have
// reset the signals to their original disposition.
block_signals(&oldset);
}
// 这里说明子进程退出了
int flags = (poll_fds[0].revents & POLLHUP) ? 0 : WNOHANG;
ret = TEMP_FAILURE_RETRY(waitpid(pid, &status, flags));
if (ret < 0) {
rc = errno;
ALOG(LOG_ERROR, "logwrap", "waitpid failed with %s\n", strerror(errno));
goto err_waitpid;
}// waitpid成功了,返回子进程的pid号码
if (ret > 0) {
found_child = true;
}
if (forward_signals) {
if (found_child) {
restore_signal_handlers();
}
unblock_signals(&oldset);
}
}
}
// chld_sts为null
if (chld_sts != nullptr) {
*chld_sts = status;
} else {// 指出子进程是否正常退出
if (WIFEXITED(status))
rc = WEXITSTATUS(status);
else
rc = -ECHILD;
}
// Flush remaining data
if (a != b) {// 输出剩下的log
buffer[b] = ‘\0‘;
log_line(&log_info, &buffer[a], b - a);
}
/* All the output has been processed, time to dump the abbreviated output */
if (abbreviated) {
print_abbr_buf(&log_info);
}
// 输出子进程退出的原因
if (WIFEXITED(status)) {
if (WEXITSTATUS(status)) {
snprintf(tmpbuf, sizeof(tmpbuf), "%s terminated by exit(%d)\n", log_info.btag,
WEXITSTATUS(status));
do_log_line(&log_info, tmpbuf);
}
} else {
if (WIFSIGNALED(status)) {
snprintf(tmpbuf, sizeof(tmpbuf), "%s terminated by signal %d\n", log_info.btag,
WTERMSIG(status));
do_log_line(&log_info, tmpbuf);
} else if (WIFSTOPPED(status)) {
snprintf(tmpbuf, sizeof(tmpbuf), "%s stopped by signal %d\n", log_info.btag,
WSTOPSIG(status));
do_log_line(&log_info, tmpbuf);
}
}
err_waitpid:
err_poll:
if (log_target & LOG_FILE) {
fclose(log_info.fp); /* Also closes underlying fd */
}
if (abbreviated) {
free_abbr_buf(&log_info.a_buf);
}
return rc;
}
1.4 log_line-输出log
static void log_line(struct log_info* log_info, char* line, int len) {
if (log_info->abbreviated) {
add_line_to_abbr_buf(&log_info->a_buf, line, len);
} else {
do_log_line(log_info, line);
}
}
1.5 do_log_line-输出log到文件,串口或者logcat上
static void do_log_line(struct log_info* log_info, const char* line) {
// 这个是串口
if (log_info->log_target & LOG_KLOG) {
klog_write(6, log_info->klog_fmt, line);
}// logcat
if (log_info->log_target & LOG_ALOG) {
ALOG(LOG_INFO, log_info->btag, "%s", line);
}// 文件上
if (log_info->log_target & LOG_FILE) {
fprintf(log_info->fp, "%s\n", line);
}
}
问题
补充
1. linux伪终端
伪终端是运行在用户态的软件仿真终端(图片来源:https://www.cnblogs.com/sparkdev/p/11605804.html)
父子进程之间通过伪终端来进行通信:子进程往slave设备写,父进程往master设备读,就可以读到子进程打印出来的log信息了
参考
1. Linux 终端(TTY)
https://www.cnblogs.com/sparkdev/p/11460821.html
2. Linux 伪终端(pty)
https://www.cnblogs.com/sparkdev/p/11605804.html