logwrapper module

概述

使用伪终端的方式来处理子进程的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信息了

image

参考

1. Linux 终端(TTY)
https://www.cnblogs.com/sparkdev/p/11460821.html
2. Linux 伪终端(pty)
https://www.cnblogs.com/sparkdev/p/11605804.html
posted @ 2021-07-06 21:00  pyjetson  阅读(557)  评论(0编辑  收藏  举报