/* * GNOME panel netwatch network status watcher module. * (C) 1997 Red Hat Software * * Author: Michael K. Johnson */ /*FIXME: port to corba!!!!*/ /* * This program is currently quite specific to Linux. * Feel free to re-implement the Linux-specific parts * on top of other operating systems, or on top of * popen("/sbin/ifconfig") or something like that. * * In addition, some pieces are specific to functionality * available with Red Hat Linux. Feel free to either work * towards more generic alternatives (the Red Hat stuff should * stay for best results on Red Hat systems) or to port the * Red Hat components (like netreport) to other Linux * distributions or other Unix variants. The tools specific * to Red Hat on which this code relies are licensed under the GPL. */ #include #ifdef __linux__ /* add others as correct */ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "panel.h" /* definitions */ #define APPLET_ID "Netwatch" #define INT_DOWN 0 #define INT_UP 1 typedef struct _Interface Interface; struct _Interface { gchar *name; GtkWidget *button; int status; }; /* globals */ static PanelCmdFunc panel_cmd_func; static GSList *interface_list; static guint update_tag; static struct sigaction old_sigio_sigaction; /* We need a fork/exec so that netreport can find this process as the * parent pid. This is a simplified fork/exec that serves our purposes. * * Why doesn't something like this exist in libc? * Sometimes I don't want to system() */ static int fork_exec(char *path, char *arg) { pid_t child; int status; if (!(child = fork())) { /* child */ execl(path, path, arg, 0); perror(path); /* Can't call exit(), because it flushes the X protocol stream */ _exit (1); } /* Hmm, I'm a parent. Might as well wait and avoid zombies, eh? */ wait4 (child, &status, 0, NULL); if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) { return 0; } else { return 1; } } #ifdef __linux__ static int has_redhat_ppp_maps() { static int is_redhat = -1; /* for now, just test if it is a Red Hat system. Other systems * that adopt the same thing can add tests for their systems here */ if (is_redhat == -1) { if (access("/etc/redhat-release", R_OK) == 0) is_redhat = 1; else is_redhat = 0; } return is_redhat; } static gchar * ppp_logical_to_physical(const gchar *logical_name) { /* way too much space for "/var/run/ppp-ppp??.dev" */ char map_file_name[100]; /* way too much space for "ppp??" */ char buffer[20]; char *p; int f, n; char *physical_device = NULL; sprintf(map_file_name, "/var/run/ppp-%s.dev", logical_name); if ((f = open(map_file_name, O_RDONLY)) > 0) { if ((n = read(f, buffer, 20)) > 0) { buffer[n] = '\0'; /* get rid of \n */ p = buffer; while (*p && *p != '\n' && p < buffer+n) p++; *p = '\0'; physical_device = g_strdup(buffer); } close(f); } return physical_device; } /* get_interface_status_by_name takes an interface name and returns * INT_DOWN or INT_UP (it doesn't know about changing; that * is handled elsewhere) * * ifconfig source code was quite helpful here. :-) */ static gint get_interface_status_by_name(const gchar *interface_name) { int sock = 0; int pfs[] = {AF_INET, AF_IPX, AF_AX25, AF_APPLETALK, 0}; int p = 0; struct ifreq ifr; gchar *physical_name = NULL; /* If we don't find it, interface must be down */ int retcode = INT_DOWN; while (!sock && pfs[p]) { sock = socket(pfs[p++], SOCK_DGRAM, 0); } if (!sock) { return INT_DOWN; } if (has_redhat_ppp_maps()) { if (!strncmp("ppp", interface_name, 3)) { /* It is a PPP device, so we need to map it from logical to physical * before we look it up. */ physical_name = ppp_logical_to_physical(interface_name); if (!physical_name) { /* If we can't find a mapping, it's because it's down. */ return INT_DOWN; } } } if (!physical_name) physical_name = g_strdup(interface_name); memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, physical_name); if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { retcode = INT_DOWN; } if (ifr.ifr_flags & IFF_UP) { retcode = INT_UP; } free(physical_name); close(sock); return retcode; } #endif /* __linux__ */ static void set_interface_status(Interface *interface) { regex_t p; regmatch_t *pmatch; gchar *interface_type = NULL; gchar *interface_name = NULL; size_t matchlen; regcomp(&p, "^([a-z]+)", REG_EXTENDED|REG_ICASE); pmatch = alloca(sizeof(regmatch_t) * p.re_nsub); regexec(&p, interface->name, p.re_nsub, pmatch, 0); matchlen = pmatch[0].rm_eo - pmatch[0].rm_so; interface_type = alloca(matchlen + 1); strncpy(interface_type, interface->name+pmatch[0].rm_so, matchlen); interface->status = get_interface_status_by_name(interface->name); if (interface->status == INT_DOWN) { interface_name = alloca(matchlen+15); sprintf(interface_name, "netwatch_%s_down", interface_type); } else { interface_name = alloca(matchlen+13); sprintf(interface_name, "netwatch_%s_up", interface_type); } gtk_widget_set_name(interface->button, interface_name); regfree(&p); } static Interface* create_interface_by_name(gchar *devname) { Interface *dev; dev = g_malloc(sizeof(Interface)); if (!dev) return NULL; dev->name = g_strdup(devname); if (!dev->name) return NULL; /* Not sure a button is the right thing; what does pressing it do? * But I want something that can be sized and take pixmaps out of * a style file automatically. I guess I want a blank_widget... * Maybe I'll come up with something for the button to do later, * like pop up a window with stats on the device? */ dev->button = gtk_button_new(); gtk_widget_show(dev->button); gtk_widget_set_usize(dev->button, 24, 48); set_interface_status(dev); return dev; } static void update_status(int ignore) { g_slist_foreach(interface_list, (GFunc) set_interface_status, NULL); } #ifdef __linux__ /* start accepting SIGIO as notification of network status * changes. Ignore failure; we'll be polling as well in any * case; this just lets us know sooner on Red Hat systems. */ static void netwatch_netreport_init (void) { struct sigaction act; act.sa_handler = update_status; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; sigaction(SIGIO, &act, &old_sigio_sigaction); fork_exec("/sbin/netreport", NULL); } static void netwatch_destroy (void) { fork_exec("/sbin/netreport", "-r"); sigaction(SIGIO, &old_sigio_sigaction, NULL); } #endif /* __linux__ */ static void properties (GtkWidget *parent_widget) { /* FIXME */ return; } static GtkWidget * create_netwatch (GtkWidget *window, char *parameters) { GtkWidget *parent_widget; Interface *this_int; gchar *devpath; gchar *dev; parent_widget = gtk_hbox_new(FALSE, 0); g_slist_free(interface_list); interface_list = NULL; devpath = gnome_config_get_string("/panel/netwatch/devpath=eth0"); if (!strlen(devpath)) return NULL; dev = strtok(devpath, " "); if (!dev) dev = devpath; do { this_int = create_interface_by_name(dev); interface_list = g_slist_append(interface_list, this_int); gtk_box_pack_end(GTK_BOX(parent_widget), this_int->button, FALSE, FALSE, 0); } while ((dev = strtok(NULL, " "))); free(devpath); #ifdef __linux__ netwatch_netreport_init(); /* we call netwatch_destroy explicitly when the applet is * destroyed, but if something goes wrong, it would be nice * to make sure it gets called anyway on a normal exit. * It doesn't hurt to call it twice. */ /* FIXME: unfortunately, we can't use atexit, because we * can't remove something from atexit's list of functions * to call. A dispatcher in panel based on a GSlist would * be nice... * Or maybe we just trust APPLET_CMD_DESTROY_MODULE... */ /* atexit(netwatch_destroy); */ #endif /* update the network status every 10 seconds */ update_tag = gtk_timeout_add (10000, (GtkFunction)update_status, NULL); return parent_widget; } static void add_devpath_component(gpointer data, gpointer user_data) { Interface *iface = data; GString **tmp_devpath = user_data; if (!*tmp_devpath) { *tmp_devpath = g_string_new(iface->name); } else { *tmp_devpath = g_string_append_c(*tmp_devpath, ' '); *tmp_devpath = g_string_append(*tmp_devpath, iface->name); } } static gchar * get_interface_path(void) { GString *new_devpath = NULL; gchar *path = NULL; g_slist_foreach(interface_list, add_devpath_component, &new_devpath); if (new_devpath){ path = g_strdup(new_devpath->str); /* caller frees */ g_string_free(new_devpath, 1); } return path; } static void create_instance (PanelWidget *panel, char *params, int pos) { GtkWidget *netwatch; PanelCommand cmd; /* for debugging */ /* sleep (600); */ netwatch = create_netwatch (GTK_WIDGET(panel), params); if (!netwatch) return; cmd.cmd = PANEL_CMD_REGISTER_TOY; cmd.params.register_toy.applet = netwatch; cmd.params.register_toy.id = APPLET_ID; cmd.params.register_toy.pos = pos; cmd.params.register_toy.flags = APPLET_HAS_PROPERTIES; (*panel_cmd_func) (&cmd); } gpointer applet_cmd_func(AppletCommand *cmd) { gchar *interface_path = NULL; g_assert(cmd != NULL); switch (cmd->cmd) { case APPLET_CMD_QUERY: return APPLET_ID; case APPLET_CMD_INIT_MODULE: panel_cmd_func = cmd->params.init_module.cmd_func; break; case APPLET_CMD_DESTROY_MODULE: interface_path = get_interface_path(); gnome_config_set_string("/panel/netwatch/devpath", interface_path); g_free(interface_path); gnome_config_sync(); #ifdef __linux__ netwatch_destroy(); #endif gtk_timeout_remove (update_tag); break; case APPLET_CMD_GET_DEFAULT_PARAMS: fprintf(stderr, "Launcher: APPLET_CMD_GET_DEFAULT_PARAMS not yet supported\n"); return g_strdup(""); /* FIXME */ case APPLET_CMD_CREATE_INSTANCE: create_instance(cmd->panel, cmd->params.create_instance.params, cmd->params.create_instance.pos); break; case APPLET_CMD_GET_INSTANCE_PARAMS: return g_strdup(gtk_object_get_user_data(GTK_OBJECT(cmd->applet))); case APPLET_CMD_ORIENTATION_CHANGE_NOTIFY: break; case APPLET_CMD_PROPERTIES: properties(cmd->applet); break; default: fprintf(stderr, APPLET_ID " applet_cmd_func: Oops, unknown command type %d\n", (int) cmd->cmd); break; } return NULL; }