/*
 * Copyright 2022 KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with thi1s program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "childrenprocesswatcher.h"
#include <QDebug>

extern "C" {
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
}

ChildrenProcessWatcher::ChildrenProcessWatcher(QObject *parent)
    : QObject(parent)
{

}

void ChildrenProcessWatcher::start()
{
    int fd = inotify_init1(IN_NONBLOCK);
    if (fd == -1) {
        qWarning() << "inotify init failed";
        return;
    }

    // Mark directories for events
    //        - file was opened
    //        - file was closed
    int pid = getpid();
    QString childrenProcessPath = QString("/proc/%1/task/%2/children").arg(pid).arg(pid);
    int wd = inotify_add_watch(fd, childrenProcessPath.toStdString().c_str(), IN_OPEN | IN_CLOSE);
    if (wd == -1) {
        qWarning() << "inotify add watch failed";
        return;
    }

    nfds_t nfds;
    struct pollfd fds[2];

    // Prepare for polling.
    nfds = 2;

    // Console input
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;

    // Inotify input
    fds[1].fd = fd;
    fds[1].events = POLLIN;

    int pollNum;
    char buf;
    for (;;) {
        pollNum = poll(fds, nfds, -1);
        if (pollNum == -1) {
            if (errno == EINTR) {
                continue;
            }
            qWarning() << "poll errno: " << errno;
            break;
        }

        if (pollNum > 0) {
            if (fds[0].revents & POLLIN) {
                // Console input is available. Empty stdin and quit.
                while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n') {
                    continue;
                }
                break;
            }

            if (fds[1].revents & POLLIN) {
                // Inotify events are available.
                handleEvents(fd);
            }
        }
    }
}

void ChildrenProcessWatcher::handleEvents(int fd)
{
    // Some systems cannot read integer variables if they are not
    // properly aligned. On other systems, incorrect alignment may
    // decrease performance. Hence, the buffer used for reading from
    // the inotify file descriptor should have the same alignment as
    // struct inotify_event.

    char buf[4096]
        __attribute__ ((aligned(__alignof__(struct inotify_event))));
    const struct inotify_event *event;
    ssize_t len;

    // Loop while events can be read from inotify file descriptor.
    for (;;) {
        // Read some events.
        len = read(fd, buf, sizeof(buf));
        if (len == -1 && errno != EAGAIN) {
            qWarning() << "read buffer error: " << errno;
            return;
        }

        // If the nonblocking read() found no events to read, then
        // it returns -1 with errno set to EAGAIN. In that case,
        // we exit the loop.
        if (len <= 0) {
            break;
        }

        // Loop over all events in the buffer.
        for (char *ptr = buf; ptr < buf + len;
                ptr += sizeof(struct inotify_event) + event->len) {
            event = (const struct inotify_event *) ptr;
            // Print event type.
            if (event->mask & IN_CLOSE_NOWRITE) {
                close(fd);
                qDebug() << "IN_CLOSE_NOWRITE: ";
            }

            if (event->mask & IN_OPEN)
                qDebug("IN_OPEN: ");

            qDebug() << event->mask;
        }
    }
}
