#include #include #include #include #include #include #include #include #include #include #include #include "gdkfbmanager.h" typedef struct { int socket; int pid; /* -1 if not initialized */ } Client; GHashTable *clients = NULL; GHashTable *new_clients = NULL; Client *current_owner = NULL; int master_socket; int create_master_socket (void) { int fd; struct sockaddr_un addr; fd = socket (PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { printf ("Error creating socket: %s\n", strerror(errno)); return -1; } unlink ("/tmp/.fb.manager"); addr.sun_family = AF_UNIX; strcpy (addr.sun_path, "/tmp/.fb.manager"); if (bind(fd, (struct sockaddr *)&addr, sizeof (addr)) < 0) { printf ("Unable to bind socket: %s\n", strerror(errno)); close (fd); return -1; } if (listen (fd, 10) < 0) { printf ("Unable to listen on socket: %s\n", strerror(errno)); close (fd); return -1; } master_socket = fd; return 0; } void handle_new_client (void) { int fd; Client *client; int true_val; fd = accept (master_socket, NULL, NULL); client = g_new (Client, 1); client->socket = fd; client->pid = -1; true_val = 1; setsockopt (fd, SOL_SOCKET, SO_PASSCRED, &true_val, sizeof (true_val)); g_print ("Handling new client %p conntecting, fd = %d\n", client, fd); g_hash_table_insert (new_clients, client, client); } struct fd_data { fd_set *read_fds; fd_set *exception_fds; int max_fd; }; void send_message (Client *client, enum FBManagerMessageType type, int data) { struct FBManagerMessage msg; msg.msg_type = type; msg.data = data; send (client->socket, &msg, sizeof (msg), 0); } gboolean wait_for_ack (Client *client, int timeout_secs) { struct FBManagerMessage msg; int res; fd_set rfds; struct timeval tv; while (1) { FD_ZERO(&rfds); FD_SET(client->socket, &rfds); tv.tv_sec = timeout_secs; tv.tv_usec = 0; res = select (client->socket+1, &rfds, NULL, NULL, &tv); if (res == 0) return FALSE; res = recv (client->socket, &msg, sizeof (msg), 0); if (res != sizeof (msg)) return FALSE; if (msg.msg_type == FB_MANAGER_ACK) return TRUE; } } void find_another_client (gpointer key, gpointer value, gpointer user_data) { Client **res; Client *client; res = user_data; if (*res) return; client = value; if (client != current_owner) *res = client; } void switch_to_client (Client *client) { g_print ("Switch_to_client, client=%p, current_owner=%p\n", client, current_owner); if ((current_owner == client) && (client != NULL)) return; if (current_owner) { g_print ("switching from client fd=%d\n", current_owner->socket); send_message (current_owner, FB_MANAGER_SWITCH_FROM, 0); wait_for_ack (current_owner, 3); } current_owner = client; if (current_owner) { g_print ("switching to client fd=%d\n", current_owner->socket); send_message (current_owner, FB_MANAGER_SWITCH_TO, 0); } } void close_client (Client *client) { Client *other_client; g_print ("Closing client %p (fd=%d)\n", client, client->socket); if (current_owner == client) { other_client = NULL; g_hash_table_foreach (clients, find_another_client, &other_client); current_owner = NULL; /* FIXME: This is a hack around the fact that the serial mouse driver had problems with opening and closing the device almost at the same time. */ sleep (1); switch_to_client (other_client); } close (client->socket); g_free (client); } /* Returns TRUE if the client was closed */ gboolean read_client_data (Client *client) { struct FBManagerMessage fb_message; struct msghdr msg; struct iovec iov; char control_buffer[256]; struct cmsghdr *cmsg; int res; struct ucred *creds; Client *new_client; iov.iov_base = &fb_message; iov.iov_len = sizeof (fb_message); cmsg = (struct cmsghdr *)control_buffer; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsg; msg.msg_controllen = 256; msg.msg_flags = 0; g_print ("Reading client data:"); res = recvmsg (client->socket, &msg, 0); g_print ("%d bytes, (error: %s)\n", res, strerror (errno)); if (res < 0) return FALSE; if (res == 0) { close_client (client); return TRUE; } if (res != sizeof (fb_message)) { g_warning ("Packet with wrong size %d recieved", res); return FALSE; } switch (fb_message.msg_type) { case FB_MANAGER_NEW_CLIENT: if (client->pid != -1) { g_warning ("Got a NEW_CLIENT message from an old client"); return FALSE; } creds = NULL; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg,cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { creds = (struct ucred *) CMSG_DATA(cmsg); break; } } if (creds == NULL) { g_warning ("Got no credentials in NEW_CLIENT message"); close_client (client); return TRUE; } client->pid = creds->pid; g_hash_table_insert (clients, GINT_TO_POINTER (client->pid), client); g_print ("New client connected. Pid=%d\n", (int)creds->pid); return TRUE; break; case FB_MANAGER_REQUEST_SWITCH_TO_PID: if (client->pid == -1) { g_warning ("Got a message from an uninitialized client"); return FALSE; } new_client = g_hash_table_lookup (clients, GINT_TO_POINTER (fb_message.data)); if (new_client) switch_to_client (new_client); else g_warning ("Switchto unknown PID"); break; case FB_MANAGER_ACK: if (client->pid == -1) { g_warning ("Got a message from an uninitialized client"); return FALSE; } g_warning ("Got an unexpected ACK"); break; default: g_warning ("Got unknown package type %d", fb_message.msg_type); break; } return FALSE; } /* Returns TRUE if the client was closed */ gboolean handle_client_data (gpointer key, gpointer value, gpointer user_data) { Client *client; struct fd_data *data; client = value; data = user_data; if (FD_ISSET (client->socket, data->exception_fds)) { close_client (client); return TRUE; } else if (FD_ISSET (client->socket, data->read_fds)) { return read_client_data (client); } return FALSE; } void set_fds (gpointer key, gpointer value, gpointer user_data) { struct fd_data *data; Client *client; client = value; data = user_data; FD_SET (client->socket, data->read_fds); FD_SET (client->socket, data->exception_fds); data->max_fd = MAX (data->max_fd, client->socket); } void main_loop (void) { fd_set read_fds; fd_set exception_fds; struct fd_data data; int res; while (1) { FD_ZERO (&read_fds); FD_ZERO (&exception_fds); FD_SET (master_socket, &read_fds); data.read_fds = &read_fds; data.exception_fds = &exception_fds; data.max_fd = master_socket; g_hash_table_foreach (clients, set_fds, &data); g_hash_table_foreach (new_clients, set_fds, &data); res = select (data.max_fd+1, &read_fds, NULL, &exception_fds, NULL); if (FD_ISSET (master_socket, &read_fds)) handle_new_client (); g_hash_table_foreach_remove (clients, handle_client_data, &data); g_hash_table_foreach_remove (new_clients, handle_client_data, &data); } } int main (int argc, char *argv[]) { clients = g_hash_table_new (g_direct_hash, g_direct_equal); new_clients = g_hash_table_new (g_direct_hash, g_direct_equal); create_master_socket (); main_loop (); return 0; }