/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "Zygote"

#include <cutils/sockets.h>
#include <cutils/zygote.h>
#include <cutils/log.h>

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define ZYGOTE_SOCKET "zygote"

#define ZYGOTE_RETRY_COUNT 1000
#define ZYGOTE_RETRY_MILLIS 500

static void replace_nl(char *str);

/*
 * If sendStdio is non-zero, the current process's stdio file descriptors
 * will be sent and inherited by the spawned process.
 */
static int send_request(int fd, int sendStdio, int argc, const char **argv)
{
#ifndef HAVE_ANDROID_OS
    // not supported on simulator targets
    //LOGE("zygote_* not supported on simulator targets");
    return -1;
#else /* HAVE_ANDROID_OS */
    uint32_t pid;
    int i;
    struct iovec ivs[2];
    struct msghdr msg;
    char argc_buffer[12];
    const char *newline_string = "\n";
    struct cmsghdr *cmsg;
    char msgbuf[CMSG_SPACE(sizeof(int) * 3)];
    int *cmsg_payload;
    ssize_t ret;

    memset(&msg, 0, sizeof(msg));
    memset(&ivs, 0, sizeof(ivs));

    // First line is arg count 
    snprintf(argc_buffer, sizeof(argc_buffer), "%d\n", argc);

    ivs[0].iov_base = argc_buffer;
    ivs[0].iov_len = strlen(argc_buffer);

    msg.msg_iov = ivs;
    msg.msg_iovlen = 1;

    if (sendStdio != 0) {
        // Pass the file descriptors with the first write
        msg.msg_control = msgbuf;
        msg.msg_controllen = sizeof msgbuf;

        cmsg = CMSG_FIRSTHDR(&msg);

        cmsg->cmsg_len = CMSG_LEN(3 * sizeof(int));
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;

        cmsg_payload = (int *)CMSG_DATA(cmsg);
        cmsg_payload[0] = STDIN_FILENO;
        cmsg_payload[1] = STDOUT_FILENO;
        cmsg_payload[2] = STDERR_FILENO;
    }

    do {
        ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
    } while (ret < 0 && errno == EINTR);

    if (ret < 0) {
        return -1;
    }

    // Only send the fd's once
    msg.msg_control = NULL;
    msg.msg_controllen = 0;

    // replace any newlines with spaces and send the args
    for (i = 0; i < argc; i++) {
        char *tofree = NULL;
        const char *toprint;

        toprint = argv[i];

        if (strchr(toprint, '\n') != NULL) {
            tofree = strdup(toprint);
            toprint = tofree;
            replace_nl(tofree);
        }

        ivs[0].iov_base = (char *)toprint;
        ivs[0].iov_len = strlen(toprint);
        ivs[1].iov_base = (char *)newline_string;
        ivs[1].iov_len = 1;

        msg.msg_iovlen = 2;

        do {
            ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
        } while (ret < 0 && errno == EINTR);

        if (tofree != NULL) {
            free(tofree);
        }

        if (ret < 0) {
            return -1;
        }
    }

    // Read the pid, as a 4-byte network-order integer

    ivs[0].iov_base = &pid;
    ivs[0].iov_len = sizeof(pid);
    msg.msg_iovlen = 1;

    do {
        do {
            ret = recvmsg(fd, &msg, MSG_NOSIGNAL | MSG_WAITALL);
        } while (ret < 0 && errno == EINTR);

        if (ret < 0) {
            return -1;
        }

        ivs[0].iov_len -= ret;
        ivs[0].iov_base += ret;
    } while (ivs[0].iov_len > 0);

    pid = ntohl(pid);

    return pid;
#endif /* HAVE_ANDROID_OS */
}

int zygote_run_wait(int argc, const char **argv, void (*post_run_func)(int))
{
    int fd;
    int pid;
    int err;
    const char *newargv[argc + 1];

    fd = socket_local_client(ZYGOTE_SOCKET, 
            ANDROID_SOCKET_NAMESPACE_RESERVED, AF_LOCAL);

    if (fd < 0) {
        return -1;
    }

    // The command socket is passed to the peer as close-on-exec
    // and will close when the peer dies
    newargv[0] = "--peer-wait";
    memcpy(newargv + 1, argv, argc * sizeof(*argv)); 

    pid = send_request(fd, 1, argc + 1, newargv);

    if (pid > 0 && post_run_func != NULL) {
        post_run_func(pid);
    }

    // Wait for socket to close
    do {
        int dummy;
        err = read(fd, &dummy, sizeof(dummy));
    } while ((err < 0 && errno == EINTR) || err != 0);

    do {
        err = close(fd);
    } while (err < 0 && errno == EINTR);

    return 0;
}

/**
 * Spawns a new dalvik instance via the Zygote process. The non-zygote
 * arguments are passed to com.android.internal.os.RuntimeInit(). The
 * first non-option argument should be a class name in the system class path.
 *
 * The arg list  may start with zygote params such as --set-uid.
 *
 * If sendStdio is non-zero, the current process's stdio file descriptors
 * will be sent and inherited by the spawned process.
 *
 * The pid of the child process is returned, or -1 if an error was 
 * encountered.
 *
 * zygote_run_oneshot waits up to ZYGOTE_RETRY_COUNT * 
 * ZYGOTE_RETRY_MILLIS for the zygote socket to be available.
 */
int zygote_run_oneshot(int sendStdio, int argc, const char **argv) 
{
    int fd = -1;
    int err;
    int i;
    int retries;
    int pid;
    const char **newargv = argv;
    const int newargc = argc;

    for (retries = 0; (fd < 0) && (retries < ZYGOTE_RETRY_COUNT); retries++) {
        if (retries > 0) { 
            struct timespec ts;

            memset(&ts, 0, sizeof(ts));
            ts.tv_nsec = ZYGOTE_RETRY_MILLIS * 1000 * 1000;

            do {
                err = nanosleep (&ts, &ts);
            } while (err < 0 && errno == EINTR);
        }
        fd = socket_local_client(ZYGOTE_SOCKET, AF_LOCAL, 
                ANDROID_SOCKET_NAMESPACE_RESERVED);
    }

    if (fd < 0) {
        return -1;
    }

    pid = send_request(fd, 0, newargc, newargv);

    do {
        err = close(fd);
    } while (err < 0 && errno == EINTR);

    return pid;
}

/**
 * Replaces all occurrances of newline with space.
 */
static void replace_nl(char *str)
{
    for(; *str; str++) {
        if (*str == '\n') {
            *str = ' ';
        }
    }
}