/* -*- mode: c; -*- */

// are we using devtmpfs on this mountpoint?
// return 1 if so
// return 0 if not
// return negative on error
static int vdev_linux_mountpoint_on_devtmpfs(char const *mountpoint)
{
    // NOTE: You would think that we can just statfs(2) the
    // mountpoint and get the underlying filesystem type.
    // However, devtmpfs and tmpfs have the same f_type value.
    // This makes this strategy a no-go, since vdevd will need
    // to behave differently on devtmpfs but not tmpfs.
    // So, we have to parse /proc/mounts instead.

    int rc = 0;

    FILE *f = NULL;
    int ent_count = 1;

    bool found = false;
    char devtmpfs_mountpoint[PATH_MAX + 1];

    char realpath_mountpoint[PATH_MAX + 1];
    char devtmpfs_realpath_mountpoint[PATH_MAX + 1];

    size_t devtmpfs_realpath_mountpoint_len = 0;
    size_t realpath_mountpoint_len = 0;

    char mntbuf[4096];
    struct mntent ment_buf;
    struct mntent *ment_ptr = NULL;

    f = fopen("/proc/mounts", "r");
    if (!f) {
        rc = -errno;
        fprintf(stderr, "Failed to open /proc/mounts, rc = %d\n", rc);
        return rc;
    }

    /* scan for sysfs mount type */
    while (1) {
        ment_ptr = getmntent_r(f, &ment_buf, mntbuf, 4096);
        if (ment_ptr == NULL)
            break;

        if (strcmp(ment_ptr->mnt_type, "devtmpfs") == 0) {
            // found!
            strncpy(devtmpfs_mountpoint, ment_ptr->mnt_dir, PATH_MAX);

            // is the mountpoint contained in devtmpfs_mountpoint?
            char *tmp = NULL;

            memset(realpath_mountpoint, 0, PATH_MAX + 1);
            memset(devtmpfs_realpath_mountpoint, 0, PATH_MAX + 1);

            tmp = realpath(mountpoint, realpath_mountpoint);
            if (!tmp) {
                rc = -errno;
                fprintf(stderr, "Failed to get the real path of '%s', rc = %d\n",
                                mountpoint, rc);
                break;
            }

            tmp = realpath(devtmpfs_mountpoint, devtmpfs_realpath_mountpoint);
            if (!tmp) {
                rc = -errno;
                fprintf(stderr, "Failed to get the real path of '%s', rc = %d\n",
                                devtmpfs_mountpoint, rc);
                break;
            }

            vdev_debug("devtmpfs realpath is   '%s'\n", devtmpfs_realpath_mountpoint);
            vdev_debug("mountpoint realpath is '%s'\n", realpath_mountpoint);

            devtmpfs_realpath_mountpoint_len = strlen(devtmpfs_realpath_mountpoint);
            realpath_mountpoint_len = strlen(realpath_mountpoint);

            size_t min_len = MIN(devtmpfs_realpath_mountpoint_len, realpath_mountpoint_len);

            // is our mountpoint within the devtmpfs mountpoint?
            // [1] -> devtmpfs mountpoint path is a substring of the mountpoint path and
            // [2] -> either the mountpoint path continues at least one directory into devtmpfs path or
            // [3] -> the devtmpfs mountpoint path *is* the mountpoint path
            if (!strncmp(devtmpfs_realpath_mountpoint, realpath_mountpoint, min_len) &&      /* [1] */
                (strchr(realpath_mountpoint + min_len, '/') != NULL ||                       /* [2] */
                strcmp(devtmpfs_realpath_mountpoint, realpath_mountpoint) == 0)) {           /* [3] */

                /* contained! */
                rc = 1;
                found = true;
                break;
            }
        }
        ent_count++;
    }

    fclose(f);

    return rc;
}

// set up Linux-specific vdev state.
// this is early initialization, so don't start anything yet
// NOTE: this should only be called from reload-safe code--i.e. a reload can't occur while this method runs
int vdev_os_init(struct vdev_os_context *os_ctx, void **cls)
{
    int rc = 0;
    struct vdev_linux_context *ctx = NULL;

    rc = vdev_linux_mountpoint_on_devtmpfs(os_ctx->state->mountpoint);
    if (rc < 0) {
        vdev_error("vdev_linux_mountpoint_on_devtmpfs('%s') rc = %d\n", os_ctx->state->mountpoint, rc);
        return rc;
    }

    if (rc > 0) {
        // using devtmpfs
        vdev_info("'%s' is on devtmpfs\n", os_ctx->state->mountpoint);
        vdev_config_set_OS_quirk(os_ctx->state->config->OS_quirks, VDEV_OS_QUIRK_DEVICE_EXISTS);
    } else {
        vdev_info("'%s' is not on devtmpfs\n", os_ctx->state->mountpoint);
    }

    ctx = VDEV_CALLOC(struct vdev_linux_context, 1);
    if (!ctx)
        return -ENOMEM;

    rc = vdev_linux_context_init(os_ctx, ctx);
    if (rc != 0) {
        free(ctx);
        return rc;
    }

    *cls = ctx;
    return 0;
}


// shut down Linux-specific vdev state
int vdev_os_shutdown(void *cls)
{
    struct vdev_linux_context *ctx = (struct vdev_linux_context*)cls;
    vdev_linux_context_shutdown(ctx);
    free(ctx);
    return 0;
}
