root/usr/src/cmd/hal/probing/xkb/probe-xkb.c
/***************************************************************************
 *
 * probe-xkb.c : Probe for keyboard device information
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Licensed under the Academic Free License version 2.1
 *
 **************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stropts.h>
#include <fcntl.h>
#include <unistd.h>
#include <priv.h>

#include <sys/kbd.h>
#include <sys/kbio.h>

#include <libhal.h>
#include <logger.h>

#define MAXLINELEN              256
#define COMMENTCHAR             '#'
#define KBD_DEFAULT_DEVICE      "/dev/kbd"
#define XKBTABLE_PATH           "/usr/X11/lib/X11/xkb/xkbtable.map"

static int              global_linenumber = 0;
static char             line[MAXLINELEN + 1];

static void
drop_privileges()
{
        priv_set_t *pPrivSet = NULL;
        priv_set_t *lPrivSet = NULL;

        /*
         * Start with the 'basic' privilege set and then remove any
         * of the 'basic' privileges that will not be needed.
         */
        if ((pPrivSet = priv_str_to_set("basic", ",", NULL)) == NULL) {
                HAL_INFO(("Error in setting the priv"));
                return;
        }

        /* Clear privileges we will not need from the 'basic' set */
        (void) priv_delset(pPrivSet, PRIV_FILE_LINK_ANY);
        (void) priv_delset(pPrivSet, PRIV_PROC_INFO);
        (void) priv_delset(pPrivSet, PRIV_PROC_SESSION);
        (void) priv_delset(pPrivSet, PRIV_PROC_EXEC);
        (void) priv_delset(pPrivSet, PRIV_PROC_FORK);

        (void) priv_addset(pPrivSet, PRIV_SYS_DEVICES);
        (void) priv_addset(pPrivSet, PRIV_FILE_DAC_READ);

        /* Set the permitted privilege set. */
        if (setppriv(PRIV_SET, PRIV_PERMITTED, pPrivSet) != 0) {
                return;
        }

        /* Clear the limit set. */
        if ((lPrivSet = priv_allocset()) == NULL) {
                return;
        }

        priv_emptyset(lPrivSet);

        if (setppriv(PRIV_SET, PRIV_LIMIT, lPrivSet) != 0) {
                return;
        }

        priv_freeset(lPrivSet);
        priv_freeset(pPrivSet);
}

static int
get_kbd_layout_type(char *device_file, int *kbd_type, int *kbd_layout)
{
        int ret = 1;
        int fd = -1;

        if ((fd = open(device_file, O_RDONLY | O_NONBLOCK)) < 0) {
                HAL_DEBUG(("Cannot open %s: %s", device_file, strerror(errno)));
                goto out;
        }

        /*
         * For usb keyboard devices, we need to first push "usbkbm" module upon
         * the stream.
         */
        if (strstr(device_file, "hid") != NULL) {
                if (ioctl(fd, I_FIND, "usbkbm") == 0) {
                        (void) ioctl(fd, I_PUSH, "usbkbm");
                        HAL_DEBUG(("usbkbm module has been pushed %s", strerror(errno)));
                }
        }

        if (ioctl(fd, KIOCTYPE, kbd_type) < 0) {
                HAL_DEBUG(("get keyboard type failed %s: %s",
                    device_file, strerror(errno)));
                goto out;
        }
        if (ioctl(fd, KIOCLAYOUT, kbd_layout) < 0) {
                HAL_DEBUG(("get keyboard layout failed %s: %s",
                    device_file, strerror(errno)));
                goto out;
        }

        ret = 0;

out:    if (fd >= 0) {
                close(fd);
        }

        return (ret);
}

/* Skips over the white space character in the string. */
static char *
skipwhite(char *ptr)
{
        while ((*ptr == ' ') || (*ptr == '\t')) {
                ptr++;
        }

        /* This should not occur. but .. */
        if (*ptr == '\n') {
                ptr = '\0';
        }

        return (ptr);
}

static char *
getaline(FILE *fp)
{
        char    *ptr;
        char    *tmp;
        int     index;
        int     c;

        while (1) {
                ptr = fgets(line, MAXLINELEN, fp);
                if (!ptr) {
                        (void) fclose(fp);
                        return (NULL);
                }

                global_linenumber++;

                /* Comment line */
                if (ptr[0] == COMMENTCHAR) {
                        continue;
                }

                /* Blank line */
                if (ptr[0] == '\n') {
                        continue;
                }

                if ((tmp = strchr(ptr, '#')) != NULL) {
                        *tmp = '\0';
                }

                if (ptr[strlen(ptr) - 1] == '\n') {
                        /* get rid of '\n' */
                        ptr[strlen(ptr) - 1] = '\0';
                }

                ptr = skipwhite(ptr);
                if (*ptr) {
                        break;
                }
        }
        return (ptr);
}

static int
sun_find_xkbnames(int kb_type, int kb_layout, char **xkb_keymap,
    char **xkb_model, char **xkb_layout)
{
        const char  *type, *layout;
        char    *keymap, *defkeymap = NULL;
        char    *model, *defmodel = NULL;
        char    *xkblay, *defxkblay = NULL;
        FILE    *fp;
        int     found_error = 0, found_keytable = 0;
        int     ret = 1;

        if ((fp = fopen(XKBTABLE_PATH, "r")) == NULL) {
                return (ret);
        }

        global_linenumber = 0;
        while (getaline(fp)) {
                if ((type = strtok(line, " \t\n")) == NULL) {
                        found_error = 1;
                }

                if ((layout = strtok(NULL, " \t\n")) == NULL) {
                        found_error = 1;
                }

                if ((keymap = strtok(NULL, " \t\n")) == NULL) {
                        found_error = 1;
                }

                /* These two are optional entries */
                model = strtok(NULL, " \t\n");
                if ((model == NULL) || (*model == COMMENTCHAR)) {
                        model = xkblay = NULL;
                } else {
                        xkblay = strtok(NULL, " \t\n");
                        if ((xkblay != NULL) && (*xkblay == COMMENTCHAR)) {
                        xkblay = NULL;
                        }
                }

                if (found_error) {
                        found_error = 0;
                        continue;
                }

                /* record default entry if/when found */
                if (*type == '*') {
                        if (defkeymap == NULL) {
                                defkeymap = strdup(keymap);
                                defmodel = strdup(model);
                                defxkblay = strdup(xkblay);
                        }
                } else if (atoi(type) == kb_type) {
                        if (*layout == '*') {
                                defkeymap = strdup(keymap);
                                defmodel = strdup(model);
                                defxkblay = strdup(xkblay);
                        } else if (atoi(layout) == kb_layout) {
                                found_keytable = 1;
                                break;
                        }
                }
        }

        (void) fclose(fp);

        if (!found_keytable) {
                keymap = defkeymap;
                model = defmodel;
                xkblay = defxkblay;
        }

        if ((keymap != NULL) && (strcmp(keymap, "-") != 0)) {
                *xkb_keymap = keymap;
        }
        if ((model != NULL) && (strcmp(model, "-") != 0)) {
                *xkb_model = model;
        }
        if ((xkblay != NULL) && (strcmp(xkblay, "-") != 0)) {
                *xkb_layout = xkblay;
        }

        return (0);
}

int
main(int argc, char *argv[])
{
        int ret = 1;
        char *udi;
        char *device_file;
        LibHalContext *ctx = NULL;
        LibHalChangeSet *cs = NULL;
        DBusError error;
        int kbd_type, kbd_layout;
        char *xkbkeymap = NULL, *xkbmodel = NULL, *xkblayout = NULL;

        if ((udi = getenv("UDI")) == NULL) {
                goto out;
        }

        if ((device_file = getenv("HAL_PROP_INPUT_DEVICE")) == NULL) {
                goto out;
        }

        drop_privileges();
        setup_logger();

        dbus_error_init(&error);
        if ((ctx = libhal_ctx_init_direct(&error)) == NULL) {
                goto out;
        }

        if ((cs = libhal_device_new_changeset(udi)) == NULL) {
                HAL_DEBUG(("Cannot allocate changeset"));
                goto out;
        }

        HAL_DEBUG(("Doing probe-xkb for %s (udi=%s)", device_file, udi));

        if (get_kbd_layout_type(device_file, &kbd_type, &kbd_layout)) {
                goto out;
        }

        /*
         * For some usb keyboard that is not self-identifying, get keyboard's
         * layout and type from system default keyboard device--/dev/kbd.
         */
        if ((kbd_layout == 0) && (strstr(device_file, "hid") != NULL)) {
                if (get_kbd_layout_type(KBD_DEFAULT_DEVICE,
                    &kbd_type, &kbd_layout)) {
                        goto out;
                }
        }

        if (sun_find_xkbnames(kbd_type, kbd_layout,
            &xkbkeymap, &xkbmodel, &xkblayout)) {
                goto out;
        }

        /*
         * If doesn't find matching entry in xkbtable.map, using default
         * values setting in 10-x11-input.fdi
         */
        if ((xkbmodel != NULL) && (xkblayout != NULL)) {
                libhal_changeset_set_property_string(cs,
                    "input.x11_options.XkbModel", xkbmodel);
                libhal_changeset_set_property_string(cs,
                    "input.x11_options.XkbLayout", xkblayout);

                libhal_device_commit_changeset(ctx, cs, &error);
        }

        ret = 0;

out:
        if (cs != NULL) {
                libhal_device_free_changeset(cs);
        }

        if (ctx != NULL) {
                libhal_ctx_shutdown(ctx, &error);
                libhal_ctx_free(ctx);
                if (dbus_error_is_set(&error)) {
                        dbus_error_free(&error);
                }
        }
        return (ret);
}