#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" #define XENCONSOLE "/usr/" LIB "/xen/bin/xenconsole" #define array_size(x) (sizeof(x)/sizeof(*x)) /* ------------------------------------------------------------------ */ GtkWidget *xd_toplevel; static GtkWidget *status; static XenDoms *store; static GtkWidget *view; static int have_screen; static int have_xenconsole; static int have_krdc; static int have_vncviewer; 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 **tty, char **os) { 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_TERMINAL, tty, XEN_DOMS_COL_S_OSTYPE, os, -1); if (0 == *id) { gtk_message(GTK_MESSAGE_ERROR, "You can't do that for Domain-0\n"); return false; } return true; } /* ------------------------------------------------------------------ */ static gboolean have_binary(char *name) { char *path, *elem, *binary; struct stat st; int rc; if (strchr(name,'/')) { /* path specified ... */ if (-1 == stat(name, &st)) return false; if (!S_ISREG(st.st_mode)) return false; if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return false; return true; } /* walk $PATH */ path = getenv("PATH"); if (NULL == path) return false; path = strdup(path); for (elem = strtok(path, ":"); NULL != elem; elem = strtok(NULL, ":")) { binary = malloc(strlen(elem)+strlen(name)+2); sprintf(binary, "%s/%s", elem, name); rc = stat(binary, &st); free(binary); if (-1 == rc) continue; if (!S_ISREG(st.st_mode)) continue; if (!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) continue; free(path); return true; } free(path); return false; } static int run_application(int do_wait, const char *app, ...) { va_list args; char *argv[64]; int status, rc, i; pid_t pid; while (waitpid(-1, &status, WNOHANG) > 0) /* collect zombies */; if (debug) fprintf(stderr,"%s: %s |", __FUNCTION__, app); va_start(args, app); for (i = 0; i < array_size(argv); i++) { argv[i] = va_arg(args, char*); if (NULL == argv[i]) break; if (debug) fprintf(stderr,"%s \"%s\"", (0 == i) ? "" : ",", argv[i]); } va_end(args); if (debug) fprintf(stderr,"\n"); if (array_size(argv) == i) { fprintf(stderr,"%s: oops: argv too small\n", __FUNCTION__); return -1; } if (0 == (pid = fork())) { /* child */ execvp(app, argv); fprintf(stderr,"%s: execvp(%s): %s\n", __FUNCTION__, app, strerror(errno)); exit(1); } if (!do_wait) return 0; rc = waitpid(pid, &status, 0); if (rc != pid) { /* Huh? */ fprintf(stderr,"%s: waidpid(%d): %s\n", __FUNCTION__, pid, strerror(errno)); exit(1); } if (!WIFEXITED(status)) return -1; return WEXITSTATUS(status); } static int attach_to_screen(gint id, char *name, char *tty) { int rc; int i; /* try attaching to a running screen ... */ rc = run_application(1, "screen", "screen", "-X", "-S", "xenconsole", "screen", "-t", name, tty, NULL); if (0 == rc) return 0; /* ... failing that start a new one ... */ rc = run_application(1, "screen", "screen", "-d", "-m", "-S", "xenconsole", "-t", name, tty, NULL); if (0 != rc) { gtk_message(GTK_MESSAGE_ERROR, "creating screen session failed\n"); return -1; } /* ... configure (loop to workaround race) ... */ for (i = 0; i < 3; i++) { rc = run_application(1, "screen", "screen", "-X", "-S", "xenconsole", "hardstatus", "lastline", "%{=b bw} xen | %-w%{yb} %n %t* %{-}%+w", NULL); if (0 == rc) break; sleep(1); } if (0 != rc) { gtk_message(GTK_MESSAGE_ERROR, "configuring screen session failed\n"); return -1; } /* new screen session created */ return 1; } static void display_screen_xterm(void) { run_application(0, "xterm", "xterm", "-name", "xenconsole", "-title", "xen consoles via screen", "-e", "screen", "-d", "-r", "xenconsole", NULL); } 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", "xenconsole", "-title", title, "-e", XENCONSOLE, ids, NULL); } static void open_tty(gint id, char *name, char *tty, int screen) { 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 (screen) { if (have_screen) { if (1 == attach_to_screen(id, name, tty)) display_screen_xterm(); } else { gtk_message(GTK_MESSAGE_ERROR, "need screen, please install\n"); } } else { if (have_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 *name, char *ostype) { char *app = NULL; char display[32]; if (have_vncviewer) { app = "vncviewer"; } else if (have_krdc) { app = "krdc"; } else { gtk_message(GTK_MESSAGE_ERROR, "need vncviewer or krdc, please install\n"); return; } if (0 == strcmp(ostype, "vmx")) { /* works for vmx ... */ snprintf(display, sizeof(display), "localhost:%d", id); run_application(0, app, app, display, NULL); } else { gtk_message(GTK_MESSAGE_ERROR, "VNC works for vmx domains only.\n"); } } static void detect_apps(void) { have_screen = have_binary("screen"); have_xenconsole = have_binary(XENCONSOLE); have_krdc = have_binary("krdc"); have_vncviewer = have_binary("vncviewer"); } /* ------------------------------------------------------------------ */ #if 0 static int xc_action(int (*func)(int xc_handle, uint32_t domid)) { char *name, *tty, *ostype; gint id = -1; int xc_handle; int rc; if (!get_domain(&id, &name, &tty, &ostype)) return -1; xc_handle = xc_interface_open(); if (-1 == xc_handle) { gtk_message(GTK_MESSAGE_ERROR, "can't open control interface\n"); return -1; } rc = func(xc_handle, id); xc_interface_close(xc_handle); if (-1 == rc) gtk_message(GTK_MESSAGE_ERROR, "xc action failed\n"); return rc; } #endif static int xend_request(char *req) { struct addrinfo ask; char *host = "localhost"; char *serv = "8000"; struct sockaddr_un unix_xend = { .sun_family = PF_UNIX, .sun_path = "/var/lib/xend/xend-socket", }; char head[256]; char body[256]; char reply[256]; int sock,lhead, lbody, rc; char *name, *tty, *ostype; gint id = -1; struct timeval tv; fd_set rd; if (!get_domain(&id, &name, &tty, &ostype)) 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: lbody = snprintf(body, sizeof(body), "%s", req); lhead = snprintf(head, sizeof(head), "POST /xend/domain/%s HTTP/1.1\r\n" "Host: %s\r\n" "Accept-Encoding: identity\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\r\n" "\r\n", name, host, lbody); write(sock, head, lhead); write(sock, body, lbody); 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, sizeof(reply)); /* FIXME: ignore response for now ... */ } else { gtk_message(GTK_MESSAGE_ERROR, "Huh, no xend reply?\n"); } close(sock); return 0; } /* ------------------------------------------------------------------ */ static void menu_cb_quit(void) { gtk_widget_destroy(xd_toplevel); } static void menu_cb_xenstore(void) { if (NULL == xs_toplevel) xenstore_create_window(); gtk_widget_show_all(xs_toplevel); } static void menu_cb_mdns(void) { #ifdef HAVE_AVAHI struct mdns_window *mdns; mdns = mdns_create_window(0, MDNS_VIEW_XEN); if (NULL == mdns) { gtk_message(GTK_MESSAGE_ERROR, "Can't setup mDNS browser, sorry.\n"); return; } mdns_browse(mdns, "_xendom._tcp", NULL); mdns_show_window(mdns); #else gtk_message(GTK_MESSAGE_ERROR, "Compiled without mDNS support, sorry.\n"); #endif } static void menu_cb_open_vnc(void) { char *name, *tty, *ostype; gint id = -1; if (!get_domain(&id, &name, &tty, &ostype)) return; if (debug) fprintf(stderr, "%s: %d\n", __FUNCTION__, id); open_vnc(id, name, ostype); } static void menu_cb_open_console(void) { char *name, *tty, *ostype; gint id = -1; if (!get_domain(&id, &name, &tty, &ostype)) return; if (debug) fprintf(stderr, "%s: %d\n", __FUNCTION__, id); open_tty(id, name, tty, 0); } static void menu_cb_attach_screen(void) { char *name, *tty, *ostype; gint id = -1; if (!get_domain(&id, &name, &tty, &ostype)) return; if (debug) fprintf(stderr, "%s: %d\n", __FUNCTION__, id); open_tty(id, name, tty, 1); } static void menu_cb_display_screen(void) { display_screen_xterm(); } static void menu_cb_domain_pause(void) { xend_request("op=pause"); } static void menu_cb_domain_unpause(void) { xend_request("op=unpause"); } static void menu_cb_domain_shutdown(void) { xend_request("op=shutdown&reason=poweroff"); } static void menu_cb_domain_reboot(void) { xend_request("op=shutdown&reason=reboot"); } static void menu_cb_domain_destroy(void) { xend_request("op=destroy"); } 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 = "DomainMenu", .label = "_Domain", },{ .name = "WindowMenu", .label = "_Window", },{ .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 (vmx domains only)", .callback = menu_cb_open_vnc, },{ .name = "OpenConsole", .label = "_Console", .accelerator = "C", .tooltip = "Open xterm with console", .callback = menu_cb_open_console, },{ .name = "AttachScreen", .label = "_Screen", .accelerator = "S", .tooltip = "Attach console to the xenconsole screen session", .callback = menu_cb_attach_screen, },{ .name = "DisplayScreen", .label = "Show screen _xterm", .tooltip = "Display xterm with the xenconsole screen session", .callback = menu_cb_display_screen, },{ .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, },{ .name = "Xenstore", .label = "_Xenstore browser", .accelerator = "X", .callback = menu_cb_xenstore, },{ .name = "mDNS", .label = "mDNS browser", .callback = menu_cb_mdns, }, }; 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); /* 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; 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_widget_new(GTK_TYPE_LABEL, "label", "status line", "xalign", 0.0, NULL); /* 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); gtk_box_pack_end(GTK_BOX(vbox), status, FALSE, TRUE, 0); detect_apps(); return; }