1 /* Required for strdup, snprintf, getopt */
2 #define _XOPEN_SOURCE 500
12 #include <sys/select.h>
14 #include <gdk/gdkkeysyms.h>
16 typedef struct sProperty
22 typedef struct sDevice
34 Parses a string of the form name=value and places the components in a Property
35 structure. Returns 0 on success, or -1 if the string wasn't a valid property.
37 int parse_property(char *str, int size, Property *prop)
42 for(i=0; (equals<0 && i<size); ++i)
49 prop->name = malloc(equals+1);
50 strncpy(prop->name, str, equals);
51 prop->name[equals] = 0;
53 prop->value = malloc(size-equals);
54 strncpy(prop->value, str+equals+1, size-equals-1);
55 prop->value[size-equals-1] = 0;
61 Retrieves all properties associated with a /dev node. The returned array is
62 terminated with an entry containing NULL values. Use free_properties to free
65 Property *get_device_properties(char *node)
73 Property *props = NULL;
83 printf("Running udevadm info -q property -n \"%s\"\n", node);
88 execl("/sbin/udevadm", "udevadm", "info", "-q", "property", "-n", node, NULL);
103 buf = (char *)malloc(bufsize);
115 len = read(pipe_fd[0], buf+pos, bufsize-pos);
124 for(i=0; (newline<0 && i<pos); ++i)
133 /* There was no newline in the buffer but there is more output to
134 be read. Try again with a larger buffer. */
136 buf = (char *)realloc(buf, bufsize);
140 if(parse_property(buf, newline, &prop)==0)
142 /* Reserve space for a sentinel value as well. */
143 props = (Property *)realloc(props, (n_props+2)*sizeof(Property));
144 props[n_props] = prop;
147 memmove(buf, buf+newline+1, pos-newline-1);
158 /* Terminate the array with NULL pointers. */
159 props[n_props].name = NULL;
160 props[n_props].value = NULL;
163 waitpid(pid, NULL, 0);
170 Looks for a property in an array of properties and returns its value. Returns
171 NULL if the property was not found.
173 char *get_property_value(Property *props, char *name)
176 for(i=0; props[i].name; ++i)
177 if(strcmp(props[i].name, name)==0)
178 return props[i].value;
183 Checks if a property has a specific value. A NULL value is matched if the
184 property does not exist.
186 int match_property_value(Property *props, char *name, char *value)
188 char *v = get_property_value(props, name);
191 return strcmp(v, value)==0;
195 Frees an array of properties and all strings contained in it.
197 void free_properties(Property *props)
202 for(i=0; props[i].name; ++i)
205 free(props[i].value);
211 Reads device names from an fstab/mtab file.
213 char **get_mount_entries(char *filename, int (*predicate)(struct mntent *))
217 char **devices = NULL;
220 file = setmntent(filename, "r");
224 while((me = getmntent(file)))
225 if(!predicate || predicate(me))
227 devices = (char **)realloc(devices, (n_devices+2)*sizeof(char *));
228 devices[n_devices] = strdup(me->mnt_fsname);
234 devices[n_devices] = NULL;
240 Returns an array of all currently mounted devices.
242 char **get_mounted_devices(void)
244 return get_mount_entries("/etc/mtab", NULL);
248 Checks if an fstab entry has the user option set.
250 int is_user_mountable(struct mntent *me)
252 return hasmntopt(me, "user")!=NULL;
256 Returns an array of user-mountable devices listed in fstab.
258 char **get_fstab_devices(void)
260 return get_mount_entries("/etc/fstab", &is_user_mountable);
264 Checks if an array of strings contains the specified string.
266 int is_in_array(char **array, char *str)
271 for(i=0; array[i]; ++i)
272 if(!strcmp(str, array[i]))
278 Frees an array of strings.
280 void free_string_array(char **array)
285 for(i=0; array[i]; ++i)
291 Checks if a partition identified by a sysfs path is on a removable device.
293 int is_removable(char *devpath)
300 len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
301 /* Default to not removable if the path was too long. */
302 if(len+10>=(int)sizeof(fnbuf))
305 /* We got a partition as a parameter, but the removable property is on the
306 disk. Replace the last component with "removable". */
307 for(ptr=fnbuf+len; (ptr>fnbuf && *ptr!='/'); --ptr) ;
308 strcpy(ptr, "/removable");
310 fd = open(fnbuf, O_RDONLY);
319 printf(" Removable\n");
323 printf(" Not removable\n");
330 Checks if a partition's disk or any of its parent devices are connected to any
331 of a set of buses. The device is identified by a sysfs path. The bus array
332 must be terminated with a NULL entry.
334 int check_buses(char *devpath, char **buses)
340 len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
341 /* Default to no match if the path was too long. */
342 if(len+10>=(int)sizeof(fnbuf))
345 for(ptr=fnbuf+len; ptr>fnbuf+12; --ptr)
349 /* Replace the last component with "subsystem". */
350 strcpy(ptr, "/subsystem");
351 len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
356 /* Extract the last component of the subsystem symlink. */
357 for(; (len>0 && linkbuf[len-1]!='/'); --len) ;
362 printf(" Subsystem of %s is %s\n", fnbuf, linkbuf+len);
365 if(is_in_array(buses, linkbuf+len))
374 Check if an array of properties describes a device that can be mounted. An
375 array of explicitly allowed devices can be passed in as well. Both arrays must
376 be terminated by a NULL entry.
378 int can_mount(Property *props, char **allowed)
380 static char *removable_buses[] = { "usb", "firewire", 0 };
385 devname = get_property_value(props, "DEVNAME");
386 if(is_in_array(allowed, devname))
389 /* Special case for CD devices, since they are not partitions. Only allow
390 mounting if media is inserted. */
391 if(match_property_value(props, "ID_TYPE", "cd") && match_property_value(props, "ID_CDROM_MEDIA", "1"))
394 /* Only allow mounting partitions. */
395 if(!match_property_value(props, "DEVTYPE", "partition"))
398 devpath = get_property_value(props, "DEVPATH");
399 if(is_removable(devpath))
402 /* Certain buses are removable by nature, but devices only advertise
403 themselves as removable if they support removable media, e.g. memory card
405 bus = get_property_value(props, "ID_BUS");
406 if(is_in_array(removable_buses, bus))
409 return check_buses(devpath, removable_buses);
413 Returns an array of all device nodes in a directory. Symbolic links are
416 char **get_device_nodes(char *dirname)
425 char **checked = NULL;
429 dir = opendir(dirname);
433 while((de = readdir(dir)))
438 /* Ignore . and .. entries. */
439 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
442 snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dirname, de->d_name);
446 if(S_ISLNK(st.st_mode))
449 len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
457 /* There may be multiple symlinks to the same device. Only include each
458 device once in the returned array. */
461 for(i=0; (!duplicate && i<n_checked); ++i)
462 if(strcmp(node, checked[i])==0)
468 printf("Device %s is a duplicate\n", fnbuf);
472 checked = (char **)realloc(checked, (n_checked+1)*sizeof(char *));
473 checked[n_checked] = strdup(node);
476 nodes = (char **)realloc(nodes, (n_nodes+2)*sizeof(char *));
477 nodes[n_nodes] = strdup(fnbuf);
484 for(i=0; i<n_checked; ++i)
490 nodes[n_nodes] = NULL;
495 /** Returns an array of all mountable devices. */
496 Device *get_devices(void)
499 Device *devices = NULL;
501 char **mounted = NULL;
505 nodes = get_device_nodes("/dev/disk/by-id");
506 mounted = get_mounted_devices();
507 fstab = get_fstab_devices();
509 for(i=0; nodes[i]; ++i)
514 printf("Examining device %s\n", nodes[i]);
516 props = get_device_properties(nodes[i]);
520 printf(" No properties\n");
527 for(j=0; props[j].name; ++j)
528 printf(" %s = %s\n", props[j].name, props[j].value);
531 if(can_mount(props, fstab))
542 printf(" Using device\n");
544 devname = get_property_value(props, "DEVNAME");
546 /* Get a human-readable label for the device. Use filesystem label,
547 filesystem UUID or device node name in order of preference. */
548 label = get_property_value(props, "ID_FS_LABEL");
550 label = get_property_value(props, "ID_FS_UUID");
556 for(ptr=label; *ptr; ++ptr)
561 vendor = get_property_value(props, "ID_VENDOR");
562 model = get_property_value(props, "ID_MODEL");
564 pos = snprintf(buf, sizeof(buf), "%s", label);
566 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
570 /* Reserve space for a sentinel entry. */
571 devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
572 devices[n_devices].node = nodes[i];
573 devices[n_devices].label = strdup(label);
574 devices[n_devices].description = strdup(buf);
575 devices[n_devices].mounted = is_in_array(mounted, devname);
576 devices[n_devices].time = st.st_mtime;
581 free_properties(props);
585 free_string_array(mounted);
589 /* Terminate the array with NULL pointers. */
590 devices[n_devices].node = NULL;
591 devices[n_devices].label = NULL;
592 devices[n_devices].description = NULL;
599 Frees an array of devices and all strings contained in it.
601 void free_devices(Device *devices)
606 for(i=0; devices[i].node; ++i)
608 free(devices[i].node);
609 free(devices[i].label);
610 free(devices[i].description);
616 Callback for activating a row in the device list. Mounts or unmounts the
617 device depending on operating mode.
619 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
623 int umount = *(int *)user_data;
628 struct timeval timeout;
633 model = gtk_tree_view_get_model(list);
635 if(!gtk_tree_model_get_iter(model, &iter, path))
638 gtk_tree_model_get(model, &iter, 1, &device, -1);
649 printf("Running pumount %s\n", device->node);
651 printf("Running pmount %s %s\n", device->node, device->label);
659 execl("/usr/bin/pumount", "pumount", device->node, NULL);
661 execl("/usr/bin/pmount", "pmount", device->node, device->label, NULL);
671 FD_SET(pipe_fd[0], &fds);
673 timeout.tv_usec = 200000;
677 /* The write fd for the pipe may be inherited by a fuse server
678 process and stay open indefinitely. */
679 if(select(pipe_fd[0]+1, &fds, NULL, NULL, &timeout))
683 len = read(pipe_fd[0], buf+pos, sizeof(buf)-pos-1);
688 else if(waitpid(pid, &status, 0))
696 waitpid(pid, &status, 0);
702 if(WIFEXITED(status))
704 if(WEXITSTATUS(status))
705 printf("Command exited successfully\n");
707 printf("Command exited with status %d\n", WEXITSTATUS(status));
709 else if(WIFSIGNALED(status))
710 printf("Command terminated with signal %d\n", WTERMSIG(status));
712 printf("Command exited with unknown result %04X\n", status);
715 if(!WIFEXITED(status) || WEXITSTATUS(status))
719 /* Pmount terminated with nonzero status or a signal. Display an
720 error to the user. */
721 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf);
722 g_signal_connect(dialog, "response", >k_main_quit, NULL);
723 gtk_widget_show_all(dialog);
733 Callback for the mount/unmount button. Causes the selected row in the device
734 list to be activated.
736 void button_clicked(GtkButton *button, gpointer user_data)
738 GtkWidget *list = (GtkWidget *)user_data;
739 GtkTreeSelection *selection;
744 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
745 gtk_tree_selection_get_selected(selection, &model, &iter);
746 path = gtk_tree_model_get_path(model, &iter);
747 gtk_tree_view_row_activated(GTK_TREE_VIEW(list), path, gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0));
748 gtk_tree_path_free(path);
754 Global key press callback for the window.
756 gboolean key_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
758 if(event->key.keyval==GDK_KEY_Escape)
770 int main(int argc, char **argv)
777 GtkTreeSelection *selection;
787 gtk_init(&argc, &argv);
789 while((opt = getopt(argc, argv, "vu"))!=-1) switch(opt)
799 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
800 gtk_container_set_border_width(GTK_CONTAINER(window), 5);
801 g_signal_connect(window, "destroy", G_CALLBACK(>k_main_quit), NULL);
802 g_signal_connect(window, "key-press-event", G_CALLBACK(&key_press), NULL);
804 box = gtk_vbox_new(FALSE, 5);
805 gtk_container_add(GTK_CONTAINER(window), box);
807 viewport = gtk_viewport_new(NULL, NULL);
808 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
809 gtk_box_pack_start(GTK_BOX(box), viewport, TRUE, TRUE, 0);
811 list = gtk_tree_view_new();
812 gtk_container_add(GTK_CONTAINER(viewport), list);
813 g_signal_connect(list, "row-activated", G_CALLBACK(&row_activated), &umount);
815 store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
816 gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
817 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list),
818 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
820 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
822 button = gtk_button_new_with_label(umount ? "Unmount" : "Mount");
823 g_signal_connect(button, "clicked", G_CALLBACK(&button_clicked), list);
824 gtk_box_pack_start(GTK_BOX(box), button, FALSE, TRUE, 0);
826 devices = get_devices();
830 /* Populate the list with devices in appropriate state. */
832 for(i=0; devices[i].node; ++i)
833 if(!devices[i].mounted==!umount)
835 gtk_list_store_append(store, &iter);
836 gtk_list_store_set(store, &iter, 0, devices[i].description, 1, &devices[i], -1);
837 if(devices[i].time>latest)
839 /* Pre-select the device that appeared on the system most recently. */
840 latest = devices[i].time;
841 gtk_tree_selection_select_iter(selection, &iter);
850 gtk_widget_show_all(window);
855 /* Don't show the main window if no devices were listed. */
856 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
857 "No devices to %s", (umount ? "unmount" : "mount"));
858 g_signal_connect(dialog, "response", G_CALLBACK(>k_main_quit), NULL);
859 gtk_widget_show_all(dialog);
864 free_devices(devices);