]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blobdiff - tests/src/libsystem.c
Add shared data manager and test
[sojka/lightdm.git] / tests / src / libsystem.c
index 9a8b6de7066e21631ef5f74b8e711e22cfebfd23..b4db0c35310bba952b2c1b0c8a2e6402c1a165e0 100644 (file)
@@ -1,18 +1,29 @@
+#define _GNU_SOURCE
+#define __USE_GNU
+
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <errno.h>
 #include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
 #include <pwd.h>
+#include <unistd.h>
+#include <dirent.h>
 #include <grp.h>
 #include <security/pam_appl.h>
-#include <unistd.h>
 #include <fcntl.h>
-#define __USE_GNU
 #include <dlfcn.h>
+#include <utmpx.h>
 #ifdef __linux__
 #include <linux/vt.h>
 #endif
 #include <glib.h>
+#include <xcb/xcb.h>
+#include <gio/gunixsocketaddress.h>
+
+#include "status.h"
 
 #define LOGIN_PROMPT "login:"
 
@@ -23,10 +34,16 @@ static GList *getpwent_link = NULL;
 
 static GList *group_entries = NULL;
 
+static int active_vt = 7;
+
+static gboolean status_connected = FALSE;
+
 struct pam_handle
 {
     char *service_name;
     char *user;
+    char *authtok;
+    char *ruser;
     char *tty;
     char **envlist;
     struct pam_conv conversation;
@@ -111,75 +128,408 @@ setgid (gid_t gid)
     return 0;
 }
 
+int
+setegid (gid_t gid)
+{
+    return 0;
+}
+
+int
+setresgid (gid_t rgid, gid_t ugid, gid_t sgid)
+{
+    return 0;
+}
+
 int
 setuid (uid_t uid)
 {
     return 0;
 }
 
+int
+seteuid (uid_t uid)
+{
+    return 0;
+}
+
+int
+setresuid (uid_t ruid, uid_t uuid, uid_t suid)
+{
+    return 0;
+}
+
+static gchar *
+redirect_path (const gchar *path)
+{
+    // Don't redirect if inside the running directory
+    if (g_str_has_prefix (path, g_getenv ("LIGHTDM_TEST_ROOT")))
+        return g_strdup (path);
+
+    if (g_str_has_prefix (path, SYSCONFDIR))
+        return g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "etc", path + strlen (SYSCONFDIR), NULL);
+
+    if (g_str_has_prefix (path, LOCALSTATEDIR))
+        return g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "var", path + strlen (LOCALSTATEDIR), NULL);
+
+    if (g_str_has_prefix (path, DATADIR))
+        return g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "usr", "share", path + strlen (DATADIR), NULL);
+
+    // Don't redirect if inside the build directory
+    if (g_str_has_prefix (path, BUILDDIR))
+        return g_strdup (path);
+
+    if (g_str_has_prefix (path, "/tmp"))
+        return g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "tmp", path + strlen ("/tmp"), NULL);
+
+    return g_strdup (path);
+}
+
 #ifdef __linux__
+static int
+open_wrapper (const char *func, const char *pathname, int flags, mode_t mode)
+{
+    int (*_open) (const char *pathname, int flags, mode_t mode);
+    gchar *new_path = NULL;
+    int fd;
+
+    _open = (int (*)(const char *pathname, int flags, mode_t mode)) dlsym (RTLD_NEXT, func);
+
+    if (strcmp (pathname, "/dev/console") == 0)
+    {
+        if (console_fd < 0)
+        {
+            console_fd = _open ("/dev/null", flags, mode);
+            fcntl (console_fd, F_SETFD, FD_CLOEXEC);
+        }
+        return console_fd;
+    }
+
+    new_path = redirect_path (pathname);
+    fd = _open (new_path, flags, mode);
+    g_free (new_path);
+
+    return fd;
+}
+
 int
 open (const char *pathname, int flags, ...)
 {
-    int (*_open) (const char * pathname, int flags, mode_t mode);
     int mode = 0;
-  
     if (flags & O_CREAT)
     {
         va_list ap;
         va_start (ap, flags);
-        mode = va_arg (ap, int);
+        mode = va_arg (ap, mode_t);
         va_end (ap);
     }
+    return open_wrapper ("open", pathname, flags, mode);
+}
 
-    _open = (int (*)(const char * pathname, int flags, mode_t mode)) dlsym (RTLD_NEXT, "open");
-    if (strcmp (pathname, "/dev/console") == 0)
+int
+open64 (const char *pathname, int flags, ...)
+{
+    int mode = 0;
+    if (flags & O_CREAT)
     {
-        if (console_fd < 0)
-        {
-            console_fd = _open ("/dev/null", flags, mode);
-            fcntl (console_fd, F_SETFD, FD_CLOEXEC);
-        }
-        return console_fd;
+        va_list ap;
+        va_start (ap, flags);
+        mode = va_arg (ap, mode_t);
+        va_end (ap);
     }
-    else if (strcmp (pathname, CONFIG_DIR "/lightdm.conf") == 0)
-    {
-        gchar *path;
-        int fd;
+    return open_wrapper ("open64", pathname, flags, mode);
+}
 
-        path = g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), "etc", "lightdm", "lightdm.conf", NULL);
-        fd = _open (path, flags, mode);
-        g_free (path);
+FILE *
+fopen (const char *path, const char *mode)
+{
+    FILE *(*_fopen) (const char *pathname, const char *mode);
+    gchar *new_path = NULL;
+    FILE *result;
 
-        return fd;
-    }
-    else
-        return _open (pathname, flags, mode);
+    _fopen = (FILE *(*)(const char *pathname, const char *mode)) dlsym (RTLD_NEXT, "fopen");
+
+    new_path = redirect_path (path);
+    result = _fopen (new_path, mode);
+    g_free (new_path);
+
+    return result;
+}
+
+int
+unlinkat (int dirfd, const char *pathname, int flags)
+{
+    int (*_unlinkat) (int dirfd, const char *pathname, int flags);
+    gchar *new_path = NULL;
+    int result;
+
+    _unlinkat = (int (*)(int dirfd, const char *pathname, int flags)) dlsym (RTLD_NEXT, "unlinkat");
+
+    new_path = redirect_path (pathname);
+    result = _unlinkat (dirfd, new_path, flags);
+    g_free (new_path);
+
+    return result;
+}
+
+int
+creat (const char *pathname, mode_t mode)
+{
+    int (*_creat) (const char *pathname, mode_t mode);
+    gchar *new_path = NULL;
+    int result;
+
+    _creat = (int (*)(const char *pathname, mode_t mode)) dlsym (RTLD_NEXT, "creat");
+
+    new_path = redirect_path (pathname);
+    result = _creat (new_path, mode);
+    g_free (new_path);
+
+    return result;
+}
+
+int
+creat64 (const char *pathname, mode_t mode)
+{
+    int (*_creat64) (const char *pathname, mode_t mode);
+    gchar *new_path = NULL;
+    int result;
+
+    _creat64 = (int (*)(const char *pathname, mode_t mode)) dlsym (RTLD_NEXT, "creat64");
+
+    new_path = redirect_path (pathname);
+    result = _creat64 (new_path, mode);
+    g_free (new_path);
+
+    return result;
+}
+
+int
+access (const char *pathname, int mode)
+{
+    int (*_access) (const char *pathname, int mode);
+    gchar *new_path = NULL;
+    int ret;
+
+    /* Look like systemd is always running */
+    if (strcmp (pathname, "/run/systemd/seats/") == 0)
+        return 1;
+
+    _access = (int (*)(const char *pathname, int mode)) dlsym (RTLD_NEXT, "access");
+
+    new_path = redirect_path (pathname);
+    ret = _access (new_path, mode);
+    g_free (new_path);
+
+    return ret;
+}
+
+int
+stat (const char *path, struct stat *buf)
+{
+    int (*_stat) (const char *path, struct stat *buf);
+    gchar *new_path = NULL;
+    int ret;
+  
+    _stat = (int (*)(const char *path, struct stat *buf)) dlsym (RTLD_NEXT, "stat");
+
+    new_path = redirect_path (path);
+    ret = _stat (new_path, buf);
+    g_free (new_path);
+
+    return ret;
+}
+
+int
+stat64 (const char *path, struct stat64 *buf)
+{
+    int (*_stat64) (const char *path, struct stat64 *buf);
+    gchar *new_path = NULL;
+    int ret;
+
+    _stat64 = (int (*)(const char *path, struct stat64 *buf)) dlsym (RTLD_NEXT, "stat64");
+
+    new_path = redirect_path (path);
+    ret = _stat64 (new_path, buf);
+    g_free (new_path);
+
+    return ret;
+}
+
+int
+__xstat (int version, const char *path, struct stat *buf)
+{
+    int (*___xstat) (int version, const char *path, struct stat *buf);
+    gchar *new_path = NULL;
+    int ret;
+  
+    ___xstat = (int (*)(int version, const char *path, struct stat *buf)) dlsym (RTLD_NEXT, "__xstat");
+
+    new_path = redirect_path (path);
+    ret = ___xstat (version, new_path, buf);
+    g_free (new_path);
+
+    return ret;
+}
+
+int
+__xstat64 (int version, const char *path, struct stat64 *buf)
+{
+    int (*___xstat64) (int version, const char *path, struct stat64 *buf);
+    gchar *new_path = NULL;
+    int ret;
+  
+    ___xstat64 = (int (*)(int version, const char *path, struct stat64 *buf)) dlsym (RTLD_NEXT, "__xstat64");
+
+    new_path = redirect_path (path);
+    ret = ___xstat64 (version, new_path, buf);
+    g_free (new_path);
+
+    return ret;
+}
+
+int
+__fxstatat(int ver, int dirfd, const char *pathname, struct stat *buf, int flags)
+{
+    int (*___fxstatat) (int ver, int dirfd, const char *pathname, struct stat *buf, int flags);
+    gchar *new_path = NULL;
+    int ret;
+  
+    ___fxstatat = (int (*)(int ver, int dirfd, const char *pathname, struct stat *buf, int flags)) dlsym (RTLD_NEXT, "__fxstatat");
+
+    new_path = redirect_path (pathname);
+    ret = ___fxstatat (ver, dirfd, new_path, buf, flags);
+    g_free (new_path);
+
+    return ret;
+}
+
+int
+__fxstatat64(int ver, int dirfd, const char *pathname, struct stat64 *buf, int flags)
+{
+    int (*___fxstatat64) (int ver, int dirfd, const char *pathname, struct stat64 *buf, int flags);
+    gchar *new_path = NULL;
+    int ret;
+  
+    ___fxstatat64 = (int (*)(int ver, int dirfd, const char *pathname, struct stat64 *buf, int flags)) dlsym (RTLD_NEXT, "__fxstatat64");
+
+    new_path = redirect_path (pathname);
+    ret = ___fxstatat64 (ver, dirfd, new_path, buf, flags);
+    g_free (new_path);
+
+    return ret;
+}
+
+DIR *
+opendir (const char *name)
+{
+    DIR *(*_opendir) (const char *name);
+    gchar *new_path = NULL;
+    DIR *result;
+
+    _opendir = (DIR *(*)(const char *name)) dlsym (RTLD_NEXT, "opendir");
+
+    new_path = redirect_path (name);
+    result = _opendir (new_path);
+    g_free (new_path);
+
+    return result; 
 }
 
 int
-ioctl (int d, int request, void *data)
+mkdir (const char *pathname, mode_t mode)
 {
-    int (*_ioctl) (int d, int request, void *data);
+    int (*_mkdir) (const char *pathname, mode_t mode);
+    gchar *new_path = NULL;
+    int result;
+
+    _mkdir = (int (*)(const char *pathname, mode_t mode)) dlsym (RTLD_NEXT, "mkdir");
+
+    new_path = redirect_path (pathname);
+    result = _mkdir (new_path, mode);
+    g_free (new_path);
+
+    return result;
+}
+
+int
+chown (const char *pathname, uid_t owner, gid_t group)
+{
+    int (*_chown) (const char *pathname, uid_t owner, gid_t group);
+    gchar *new_path = NULL;
+    int result;
 
-    _ioctl = (int (*)(int d, int request, void *data)) dlsym (RTLD_NEXT, "ioctl");
+    _chown = (int (*)(const char *pathname, uid_t owner, gid_t group)) dlsym (RTLD_NEXT, "chown");
+
+    new_path = redirect_path (pathname);
+    result = _chown (new_path, owner, group);
+    g_free (new_path);
+
+    return result;
+}
+
+int
+chmod (const char *path, mode_t mode)
+{
+    int (*_chmod) (const char *path, mode_t mode);
+    gchar *new_path = NULL;
+    int result;
+
+    _chmod = (int (*)(const char *path, mode_t mode)) dlsym (RTLD_NEXT, "chmod");
+
+    new_path = redirect_path (path);
+    result = _chmod (new_path, mode);
+    g_free (new_path);
+
+    return result;
+}
+
+int
+ioctl (int d, unsigned long request, ...)
+{
+    int (*_ioctl) (int d, int request, ...);
+
+    _ioctl = (int (*)(int d, int request, ...)) dlsym (RTLD_NEXT, "ioctl");
     if (d > 0 && d == console_fd)
     {
         struct vt_stat *console_state;
+        int vt;
+        va_list ap;
 
         switch (request)
         {
         case VT_GETSTATE:
-            console_state = data;
-            console_state->v_active = 7;
-            break;          
+            va_start (ap, request);
+            console_state = va_arg (ap, struct vt_stat *);
+            va_end (ap);
+            console_state->v_active = active_vt;
+            break;
         case VT_ACTIVATE:
+            va_start (ap, request);
+            vt = va_arg (ap, int);
+            va_end (ap);
+            if (vt != active_vt)
+            {
+                active_vt = vt;
+                if (!status_connected)
+                    status_connected = status_connect (NULL);
+                status_notify ("VT ACTIVATE VT=%d", active_vt);
+            }
+            break;
+        case VT_WAITACTIVE:
             break;
         }
         return 0;
     }
     else
+    {
+        va_list ap;
+        void *data;
+
+        va_start (ap, request);
+        data = va_arg (ap, void *);
+        va_end (ap);
         return _ioctl (d, request, data);
+    }
 }
 
 int
@@ -209,7 +559,7 @@ free_user (gpointer data)
 }
 
 static void
-load_passwd_file ()
+load_passwd_file (void)
 {
     gchar *path, *data = NULL, **lines;
     gint i;
@@ -341,7 +691,7 @@ free_group (gpointer data)
 }
 
 static void
-load_group_file ()
+load_group_file (void)
 {
     gchar *path, *data = NULL, **lines;
     gint i;
@@ -436,6 +786,8 @@ pam_start (const char *service_name, const char *user, const struct pam_conv *co
 
     handle->service_name = strdup (service_name);
     handle->user = user ? strdup (user) : NULL;
+    handle->authtok = NULL;
+    handle->ruser = NULL;
     handle->tty = NULL;
     handle->conversation.conv = conversation->conv;
     handle->conversation.appdata_ptr = conversation->appdata_ptr;
@@ -474,6 +826,68 @@ pam_authenticate (pam_handle_t *pamh, int flags)
 
     if (pamh == NULL)
         return PAM_SYSTEM_ERR;
+  
+    if (strcmp (pamh->service_name, "test-remote") == 0)
+    {
+        int result;
+        struct pam_message **msg;
+        struct pam_response *resp = NULL;
+
+        msg = malloc (sizeof (struct pam_message *) * 1);
+        msg[0] = malloc (sizeof (struct pam_message));
+        msg[0]->msg_style = PAM_PROMPT_ECHO_ON; 
+        msg[0]->msg = "remote-login:";
+        result = pamh->conversation.conv (1, (const struct pam_message **) msg, &resp, pamh->conversation.appdata_ptr);
+        free (msg[0]);
+        free (msg);
+        if (result != PAM_SUCCESS)
+            return result;
+
+        if (resp == NULL)
+            return PAM_CONV_ERR;
+        if (resp[0].resp == NULL)
+        {
+            free (resp);
+            return PAM_CONV_ERR;
+        }
+
+        if (pamh->ruser)
+            free (pamh->ruser);
+        pamh->ruser = strdup (resp[0].resp);
+        free (resp[0].resp);
+        free (resp);
+
+        msg = malloc (sizeof (struct pam_message *) * 1);
+        msg[0] = malloc (sizeof (struct pam_message));
+        msg[0]->msg_style = PAM_PROMPT_ECHO_OFF;
+        msg[0]->msg = "remote-password:";
+        result = pamh->conversation.conv (1, (const struct pam_message **) msg, &resp, pamh->conversation.appdata_ptr);
+        free (msg[0]);
+        free (msg);
+        if (result != PAM_SUCCESS)
+            return result;
+
+        if (resp == NULL)
+            return PAM_CONV_ERR;
+        if (resp[0].resp == NULL)
+        {
+            free (resp);
+            return PAM_CONV_ERR;
+        }
+
+        if (pamh->authtok)
+            free (pamh->authtok);
+        pamh->authtok = strdup (resp[0].resp);
+        free (resp[0].resp);
+        free (resp);
+
+        password_matches = strcmp (pamh->ruser, "remote-user") == 0 && strcmp (pamh->authtok, "password") == 0;
+
+        if (password_matches)
+            return PAM_SUCCESS;
+        else
+            return PAM_AUTH_ERR;
+    }
 
     /* Prompt for username */
     if (pamh->user == NULL)
@@ -547,6 +961,13 @@ pam_authenticate (pam_handle_t *pamh, int flags)
             msg[n_messages]->msg = "You should have seen three messages";
             n_messages++;
         }
+        if (strcmp (pamh->user, "multi-prompt") == 0)
+        {
+            msg[n_messages] = malloc (sizeof (struct pam_message));
+            msg[n_messages]->msg_style = PAM_PROMPT_ECHO_ON;
+            msg[n_messages]->msg = "Favorite Color:";
+            n_messages++;
+        }
         msg[n_messages] = malloc (sizeof (struct pam_message));
         msg[n_messages]->msg_style = PAM_PROMPT_ECHO_OFF;
         msg[n_messages]->msg = "Password:";
@@ -569,6 +990,10 @@ pam_authenticate (pam_handle_t *pamh, int flags)
 
         if (entry)
             password_matches = strcmp (entry->pw_passwd, resp[password_index].resp) == 0;
+
+        if (password_matches && strcmp (pamh->user, "multi-prompt") == 0)
+            password_matches = strcmp ("blue", resp[0].resp) == 0;
+
         for (i = 0; i < n_messages; i++)
         {
             if (resp[i].resp)
@@ -629,9 +1054,9 @@ static const char *
 get_env_value (const char *name_value, const char *name)
 {
     int j;
-
-    for (j = 0; name[j] && name[j] != '=' && name[j] == name_value[j]; j++);
-    if (name_value[j] == '=')
+  
+    for (j = 0; name[j] && name_value[j] && name[j] == name_value[j]; j++);
+    if (name[j] == '\0' && name_value[j] == '=')
         return &name_value[j + 1];
 
     return NULL;
@@ -641,15 +1066,21 @@ int
 pam_putenv (pam_handle_t *pamh, const char *name_value)
 {
     int i;
+    gchar *name;
 
     if (pamh == NULL || name_value == NULL)
         return PAM_SYSTEM_ERR;
 
+    name = strdup (name_value);
+    for (i = 0; name[i]; i++)
+        if (name[i] == '=')
+            name[i] = '\0';
     for (i = 0; pamh->envlist[i]; i++)
     {
-        if (get_env_value (pamh->envlist[i], name_value))
+        if (get_env_value (pamh->envlist[i], name))
             break;
     }
+    free (name);
 
     if (pamh->envlist[i])
     {
@@ -728,7 +1159,15 @@ pam_get_item (const pam_handle_t *pamh, int item_type, const void **item)
     case PAM_USER:
         *item = pamh->user;
         return PAM_SUCCESS;
-      
+
+    case PAM_AUTHTOK:
+        *item = pamh->authtok;
+        return PAM_SUCCESS;
+
+    case PAM_RUSER:
+        *item = pamh->ruser;
+        return PAM_SUCCESS;
+     
     case PAM_USER_PROMPT:
         *item = LOGIN_PROMPT;
         return PAM_SUCCESS;
@@ -819,7 +1258,10 @@ pam_chauthtok (pam_handle_t *pamh, int flags)
     msg = malloc (sizeof (struct pam_message *) * 1);
     msg[0] = malloc (sizeof (struct pam_message));
     msg[0]->msg_style = PAM_PROMPT_ECHO_OFF;
-    msg[0]->msg = "Enter new password:";
+    if ((flags & PAM_CHANGE_EXPIRED_AUTHTOK) != 0)
+        msg[0]->msg = "Enter new password (expired):";
+    else
+        msg[0]->msg = "Enter new password:";
     result = pamh->conversation.conv (1, (const struct pam_message **) msg, &resp, pamh->conversation.appdata_ptr);
     free (msg[0]);
     free (msg);
@@ -877,8 +1319,12 @@ pam_setcred (pam_handle_t *pamh, int flags)
         if (group)
         {
             groups_length = getgroups (0, NULL);
+            if (groups_length < 0)
+                return PAM_SYSTEM_ERR;
             groups = malloc (sizeof (gid_t) * (groups_length + 1));
             groups_length = getgroups (groups_length, groups);
+            if (groups_length < 0)
+                return PAM_SYSTEM_ERR;
             groups[groups_length] = group->gr_gid;
             groups_length++;
             setgroups (groups_length, groups);
@@ -901,6 +1347,10 @@ pam_end (pam_handle_t *pamh, int pam_status)
     free (pamh->service_name);
     if (pamh->user)
         free (pamh->user);
+    if (pamh->authtok)
+        free (pamh->authtok);
+    if (pamh->ruser)
+        free (pamh->ruser);
     if (pamh->tty)
         free (pamh->tty);
     free (pamh);
@@ -984,3 +1434,101 @@ pam_strerror (pam_handle_t *pamh, int errnum)
         return "Unknown PAM error";
     }
 }
+
+void
+setutxent (void)
+{
+}
+  
+struct utmpx *
+pututxline (const struct utmpx *ut)
+{
+    return (struct utmpx *)ut;
+}
+
+void
+endutxent (void)
+{
+}
+
+struct xcb_connection_t
+{
+    gchar *display;
+    int error;
+    GSocket *socket;
+};
+
+xcb_connection_t *
+xcb_connect_to_display_with_auth_info (const char *display, xcb_auth_info_t *auth, int *screen)
+{
+    xcb_connection_t *c;
+    gchar *socket_path;
+    GError *error = NULL;
+  
+    c = malloc (sizeof (xcb_connection_t));
+    c->display = g_strdup (display);
+    c->error = 0;
+
+    if (display == NULL)
+        display = getenv ("DISPLAY");
+    if (display == NULL)
+        c->error = XCB_CONN_CLOSED_PARSE_ERR;
+
+    if (c->error == 0)
+    {
+        c->socket = g_socket_new (G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, G_SOCKET_PROTOCOL_DEFAULT, &error);
+        if (error)
+            g_printerr ("%s\n", error->message);
+        g_clear_error (&error);
+        if (c->socket == NULL)
+            c->error = XCB_CONN_ERROR;
+    }
+
+    if (c->error == 0)
+    {
+        gchar *d;
+        GSocketAddress *address;
+
+        /* Skip the hostname, we'll assume it's localhost */
+        d = g_strdup_printf (".x%s", strchr (display, ':'));
+
+        socket_path = g_build_filename (g_getenv ("LIGHTDM_TEST_ROOT"), d, NULL);
+        g_free (d);
+        address = g_unix_socket_address_new (socket_path);
+        if (!g_socket_connect (c->socket, address, NULL, &error))
+            c->error = XCB_CONN_ERROR;
+        g_object_unref (address);
+        if (error)
+            g_printerr ("Failed to connect to X socket %s: %s\n", socket_path, error->message);
+        g_free (socket_path);
+        g_clear_error (&error);
+    }
+
+    // FIXME: Send auth info
+    if (c->error == 0)
+    {
+    }
+
+    return c;
+}
+
+xcb_connection_t *
+xcb_connect (const char *displayname, int *screenp)
+{
+    return xcb_connect_to_display_with_auth_info(displayname, NULL, screenp);
+}
+
+int
+xcb_connection_has_error (xcb_connection_t *c)
+{
+    return c->error;
+}
+
+void
+xcb_disconnect (xcb_connection_t *c)
+{
+    free (c->display);
+    if (c->socket)
+        g_object_unref (c->socket);
+    free (c);
+}