#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "xd_store.h" #include "xenviews.h" #include "tcp.h" #include "mdns.h" #include "apps.h" #include "vnc.h" #define array_size(x) (sizeof(x)/sizeof(*x)) /* ------------------------------------------------------------------ */ GtkWidget *xd_toplevel; static GtkWidget *status; static XenDoms *store; static GtkWidget *view; static int debug = 0; /* ------------------------------------------------------------------ */ static char *gtk_msg_type_name[] = { [ GTK_MESSAGE_INFO ] = "INFO", [ GTK_MESSAGE_WARNING ] = "WARNING", [ GTK_MESSAGE_QUESTION ] = "QUESTION", [ GTK_MESSAGE_ERROR ] = "ERROR", }; static int __attribute__ ((format (printf, 2, 0))) gtk_message(GtkMessageType type, char *fmt, ...) { va_list args; GtkWidget *dialog; char msgbuf[1024]; int rc; va_start(args, fmt); rc = vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); va_end(args); if (debug) fprintf(stderr, "%s: %s", gtk_msg_type_name[type], msgbuf); dialog = gtk_message_dialog_new(GTK_WINDOW(xd_toplevel), GTK_DIALOG_DESTROY_WITH_PARENT, type, GTK_BUTTONS_CLOSE, "%s", msgbuf); g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show_all(dialog); return rc; } static gboolean get_domain(gint *id, char **name, char **os, char **tty, gint *vncport) { GtkTreeSelection *sel; GtkTreeModel *model; GtkTreeIter iter; sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); if (!gtk_tree_selection_get_selected(sel, &model, &iter)) { gtk_message(GTK_MESSAGE_ERROR, "No domain selected\n"); return false; } gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, XEN_DOMS_COL_I_ID, id, XEN_DOMS_COL_S_NAME, name, XEN_DOMS_COL_S_OSTYPE, os, XEN_DOMS_COL_S_TERMINAL, tty, XEN_DOMS_COL_I_VNCPORT, vncport, -1); if (0 == *id) { gtk_message(GTK_MESSAGE_ERROR, "You can't do that for Domain-0\n"); return false; } return true; } /* ------------------------------------------------------------------ */ static void open_xenconsole(gint id, char *name, char *tty) { char title[64], ids[8]; snprintf(title, sizeof(title), "xen console: %s (%d)", name, id); snprintf(ids, sizeof(ids), "%d", id); run_application(0, "xterm", "xterm", "-name", "console_xterm", "-title", title, "-e", XENCONSOLE, ids, NULL); } static void open_tty(gint id, char *name, char *tty) { int rc; /* sanity checks */ if (0 != access(tty, R_OK)) { gtk_message(GTK_MESSAGE_ERROR, "no access to tty %s\n", tty); return; } rc = run_application(1, "fuser", "fuser", "-s", tty, NULL); if (0 == rc) { gtk_message(GTK_MESSAGE_ERROR, "tty %s already in use\n", tty); return; } /* open terminal */ if (have_application(XENCONSOLE)) { open_xenconsole(id, name, tty); } else { gtk_message(GTK_MESSAGE_ERROR, "need xen-tools, please install\n"); } } static void open_vnc(gint id, char *hostname, gint tcpport) { #ifdef HAVE_GTK_VNC if (1) { vnc_open(hostname, tcpport, VNC_FLAG_SHOW_MOUSE | VNC_FLAG_DISCONNECT_CLOSE, 0); return; } #endif if (-1 == open_vnc_session(hostname, tcpport)) gtk_message(GTK_MESSAGE_ERROR, app_error); } /* ------------------------------------------------------------------ */ static int xmlrpc_request(char *method, ...) { struct addrinfo ask; char *host = "localhost"; char *serv = "8005"; struct sockaddr_un unix_xend = { .sun_family = PF_UNIX, .sun_path = "/var/run/xend/xmlrpc.sock", }; va_list args; char head[256]; char body[1024]; char reply[1024]; int sock,lhead, lbody, lreply, rc, eof; char *name, *tty, *ostype, *arg; gint id = -1, vncport; struct timeval tv; fd_set rd; if (!get_domain(&id, &name, &ostype, &tty, &vncport)) return -1; /* try tcp first */ memset(&ask,0,sizeof(ask)); ask.ai_socktype = SOCK_STREAM; ask.ai_family = PF_UNSPEC; sock = tcp_connect(&ask, NULL, NULL, host, serv); if (0 == sock) goto connected; /* failing that unix sockets */ sock = socket(PF_UNIX, SOCK_STREAM, 0); if (-1 == connect(sock, (struct sockaddr*)&unix_xend, sizeof(unix_xend))) { gtk_message(GTK_MESSAGE_ERROR, "can't connect to xend\n"); return -1; } connected: /* req start + method name */ lbody = snprintf(body, sizeof(body), "\n" "\n" " %s\n" " \n", method); /* domain */ lbody += snprintf(body + lbody, sizeof(body) - lbody, " \n" " %d\n" " \n", id); /* maybe more args */ va_start(args, method); for (;;) { arg = va_arg(args, char*); if (NULL == arg) break; lbody += snprintf(body + lbody, sizeof(body) - lbody, " \n" " %s\n" " \n", arg); } va_end(args); /* finish off */ lbody += snprintf(body + lbody, sizeof(body) - lbody, " \n" "\n"); if (debug) write(2, body, lbody); /* http header */ lhead = snprintf(head, sizeof(head), "POST /RPC2 HTTP/1.0\r\n" "Host: %s\r\n" "User-Agent: xenwatch\r\n" "Content-Type: text/xml\r\n" "Content-Length: %d\r\n" "\r\n", host, lbody); write(sock, head, lhead); write(sock, body, lbody); for (lreply = 0, eof = 0; !eof;) { FD_ZERO(&rd); FD_SET(sock, &rd); tv.tv_sec = 3; tv.tv_usec = 0; if (1 == select(sock+1, &rd, NULL, NULL, &tv)) { rc = read(sock, reply + lreply, sizeof(reply) - lreply -1); switch (rc) { case -1: perror("read"); /* fall through */ case 0: eof = 1; break; default: lreply += rc; reply[lreply] = 0; break; } } } close(sock); if (!lreply) { gtk_message(GTK_MESSAGE_ERROR, "Huh, no xend reply?\n"); return -1; } if (NULL != strstr(reply, "")) { gtk_message(GTK_MESSAGE_ERROR, "XMLRPC call failed:\n\n%s", reply); return -1; } if (debug) write(2, reply, lreply); return 0; } /* ------------------------------------------------------------------ */ static void menu_cb_quit(void) { gtk_widget_destroy(xd_toplevel); } static void menu_cb_open_vnc(void) { char *name, *tty, *ostype; gint id = -1, vncport, tcpport = -1; if (!get_domain(&id, &name, &ostype, &tty, &vncport)) return; if (debug) fprintf(stderr, "%s: %d\n", __FUNCTION__, id); if (vncport) /* xen 3.0.3+ */ tcpport = vncport; else if (0 == strcmp(ostype, "hvm")) { /* xen 3.0.2 */ tcpport = id + 5900; } if (-1 != tcpport) open_vnc(id, "localhost", tcpport); else gtk_message(GTK_MESSAGE_ERROR, "Domain has no graphical display.\n"); } static void menu_cb_open_console(void) { char *name, *tty, *ostype; gint id = -1, vncport; if (!get_domain(&id, &name, &ostype, &tty, &vncport)) return; if (debug) fprintf(stderr, "%s: %d\n", __FUNCTION__, id); open_tty(id, name, tty); } static void menu_cb_domain_pause(void) { xmlrpc_request("xend.domain.pause", NULL); } static void menu_cb_domain_unpause(void) { xmlrpc_request("xend.domain.unpause", NULL); } static void menu_cb_domain_shutdown(void) { xmlrpc_request("xend.domain.shutdown", "poweroff", NULL); } static void menu_cb_domain_reboot(void) { xmlrpc_request("xend.domain.shutdown", "reboot", NULL); } static void menu_cb_domain_destroy(void) { xmlrpc_request("xend.domain.destroy", NULL); } static void menu_cb_about(void) { static char *comments = "xen domain monitor"; static char *copyright = "(c) 2005-2006 Gerd Hoffmann"; static char *authors[] = { "Gerd Hoffmann ", NULL }; gtk_show_about_dialog(GTK_WINDOW(xd_toplevel), "authors", authors, "comments", comments, "copyright", copyright, "logo-icon-name", GTK_STOCK_ABOUT, "version", VERSION, NULL); } static void destroy(void) { g_object_unref(store); xd_toplevel = NULL; gtk_main_quit(); } /* ------------------------------------------------------------------ */ static const GtkActionEntry entries[] = { { /* menus */ .name = "FileMenu", .label = "_File", },{ .name = "ConnectMenu", .label = "_Connect", },{ .name = "DomainMenu", .label = "_Domain", },{ .name = "HelpMenu", .label = "_Help", },{ /* menu items */ .name = "Quit", .stock_id = GTK_STOCK_QUIT, .label = "_Quit", .accelerator = "Q", .tooltip = "Quit the job", .callback = menu_cb_quit, },{ .name = "About", .stock_id = GTK_STOCK_ABOUT, .label = "_About ...", .callback = menu_cb_about, },{ .name = "OpenVNC", .label = "_VNC", .accelerator = "V", .tooltip = "Open VNC viewer (hvm domains only)", .callback = menu_cb_open_vnc, },{ .name = "OpenConsole", .label = "_Console", .accelerator = "C", .tooltip = "Open xterm with console", .callback = menu_cb_open_console, },{ .name = "DomainPause", .stock_id = GTK_STOCK_MEDIA_PAUSE, .label = "_Pause", .tooltip = "Pause domain", .callback = menu_cb_domain_pause, },{ .name = "DomainUnpause", .stock_id = GTK_STOCK_MEDIA_PLAY, .label = "_Unpause", .tooltip = "Unpause domain", .callback = menu_cb_domain_unpause, },{ .name = "DomainReboot", .stock_id = GTK_STOCK_REFRESH, .label = "_Reboot", .accelerator = "R", .tooltip = "Reboot domain", .callback = menu_cb_domain_reboot, },{ .name = "DomainShutdown", .stock_id = GTK_STOCK_STOP, .label = "_Shutdown", .tooltip = "Graceful shutdown of the domain", .callback = menu_cb_domain_shutdown, },{ .name = "DomainDestroy", .stock_id = GTK_STOCK_DELETE, .label = "Destroy", .tooltip = "Radically kill off domain", .callback = menu_cb_domain_destroy, }, }; static char ui_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " #if 0 " " " " " " #endif " " " " " " ""; /* ------------------------------------------------------------------ */ static void activate(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data) { GtkTreeIter iter; gint id; if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path)) return; gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, XEN_DOMS_COL_I_ID, &id, -1); if (debug) fprintf(stderr, "%s: %d\n", __FUNCTION__, id); /* TODO: something useful ;) */ } static GtkWidget *xen_doms_create_view(XenDoms *store) { GtkCellRenderer *renderer; GtkWidget *view; view = gtk_tree_view_new(); gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), GTK_SELECTION_SINGLE); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("name"), renderer, "text", XEN_DOMS_COL_S_NAME, NULL); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "xalign", 1.0, NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("id"), renderer, "text", XEN_DOMS_COL_I_ID, NULL); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "xalign", 1.0, NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("mem"), renderer, "text", XEN_DOMS_COL_I_MEM, NULL); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "xalign", 1.0, NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("max"), renderer, "text", XEN_DOMS_COL_I_MAXMEM, NULL); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "xalign", 1.0, NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("cpus"), renderer, "text", XEN_DOMS_COL_I_CPUS, NULL); renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "xalign", 1.0, NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("max"), renderer, "text", XEN_DOMS_COL_I_MAXCPUS, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("os"), renderer, "text", XEN_DOMS_COL_S_OSTYPE, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("tty"), renderer, "text", XEN_DOMS_COL_S_TERMINAL, NULL); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _("vnc"), renderer, "text", XEN_DOMS_COL_I_VNCPORT, NULL); /* fill remaining space */ renderer = gtk_cell_renderer_text_new(); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(view), -1, _(""), renderer, NULL); return view; } void xen_doms_create_window(void) { GtkWidget *vbox, *menubar, *toolbar, *scroll, *frame; GtkAccelGroup *accel; GtkActionGroup *ag; GtkUIManager *ui; GError *err; xd_toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(xd_toplevel), _("xendoms")); gtk_widget_set_size_request(GTK_WIDGET(xd_toplevel), 480, 320); g_signal_connect(G_OBJECT(xd_toplevel), "destroy", G_CALLBACK(destroy), NULL); /* menu + toolbar */ ui = gtk_ui_manager_new(); ag = gtk_action_group_new("MenuActions"); gtk_action_group_add_actions(ag, entries, G_N_ELEMENTS(entries), xd_toplevel); gtk_ui_manager_insert_action_group(ui, ag, 0); accel = gtk_ui_manager_get_accel_group(ui); gtk_window_add_accel_group(GTK_WINDOW(xd_toplevel), accel); err = NULL; if (!gtk_ui_manager_add_ui_from_string(ui, ui_xml, -1, &err)) { g_message("building menus failed: %s", err->message); g_error_free(err); exit(1); } /* list */ store = xen_doms_new(); view = xen_doms_create_view(store); g_signal_connect(view, "row-activated", G_CALLBACK(activate), NULL); scroll = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); /* other widgets */ status = gtk_label_new("status line"); gtk_misc_set_alignment(GTK_MISC(status), 0, 0.5); gtk_misc_set_padding(GTK_MISC(status), 3, 1); /* Make a vbox and put stuff in */ vbox = gtk_vbox_new(FALSE, 1); gtk_container_set_border_width(GTK_CONTAINER(vbox), 1); gtk_container_add(GTK_CONTAINER(xd_toplevel), vbox); menubar = gtk_ui_manager_get_widget(ui, "/MainMenu"); gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); toolbar = gtk_ui_manager_get_widget(ui, "/ToolBar"); gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0); gtk_container_add(GTK_CONTAINER(scroll), view); frame = gtk_frame_new(NULL); gtk_box_pack_end(GTK_BOX(vbox), frame, FALSE, TRUE, 0); gtk_container_add(GTK_CONTAINER(frame), status); return; }