1 /* Required for strdup, snprintf, getopt */
2 #define _XOPEN_SOURCE 500
13 #include <sys/select.h>
15 #include <gdk/gdkkeysyms.h>
17 typedef struct sProperty
23 typedef struct sDevice
34 char *post_mount_command = NULL;
35 char **pmount_argv = NULL;
38 Parses a string of the form name=value and places the components in a Property
39 structure. Returns 0 on success, or -1 if the string wasn't a valid property.
41 int parse_property(char *str, int size, Property *prop)
46 for(i=0; (equals<0 && i<size); ++i)
53 prop->name = malloc(equals+1);
54 strncpy(prop->name, str, equals);
55 prop->name[equals] = 0;
57 prop->value = malloc(size-equals);
58 strncpy(prop->value, str+equals+1, size-equals-1);
59 prop->value[size-equals-1] = 0;
65 Retrieves all properties associated with a /dev node. The returned array is
66 terminated with an entry containing NULL values. Use free_properties to free
69 Property *get_device_properties(char *node)
77 Property *props = NULL;
87 printf("Running udevadm info -q property -n \"%s\"\n", node);
92 execl("/sbin/udevadm", "udevadm", "info", "-q", "property", "-n", node, NULL);
107 buf = (char *)malloc(bufsize);
119 len = read(pipe_fd[0], buf+pos, bufsize-pos);
128 for(i=0; (newline<0 && i<pos); ++i)
137 /* There was no newline in the buffer but there is more output to
138 be read. Try again with a larger buffer. */
140 buf = (char *)realloc(buf, bufsize);
144 if(parse_property(buf, newline, &prop)==0)
146 /* Reserve space for a sentinel value as well. */
147 props = (Property *)realloc(props, (n_props+2)*sizeof(Property));
148 props[n_props] = prop;
151 memmove(buf, buf+newline+1, pos-newline-1);
162 /* Terminate the array with NULL pointers. */
163 props[n_props].name = NULL;
164 props[n_props].value = NULL;
167 waitpid(pid, NULL, 0);
174 Looks for a property in an array of properties and returns its value. Returns
175 NULL if the property was not found.
177 char *get_property_value(Property *props, char *name)
180 for(i=0; props[i].name; ++i)
181 if(strcmp(props[i].name, name)==0)
182 return props[i].value;
187 Checks if a property has a specific value. A NULL value is matched if the
188 property does not exist.
190 int match_property_value(Property *props, char *name, char *value)
192 char *v = get_property_value(props, name);
195 return strcmp(v, value)==0;
199 Frees an array of properties and all strings contained in it.
201 void free_properties(Property *props)
206 for(i=0; props[i].name; ++i)
209 free(props[i].value);
215 Returns an array of user-mountable devices listed in fstab.
217 char **get_fstab_devices(void)
221 char **devices = NULL;
224 file = setmntent("/etc/fstab", "r");
228 while((me = getmntent(file)))
229 if(hasmntopt(me, "user")!=NULL)
231 devices = (char **)realloc(devices, (n_devices+2)*sizeof(char *));
232 devices[n_devices] = strdup(me->mnt_fsname);
238 devices[n_devices] = NULL;
244 Checks if an array of strings contains the specified string.
246 int is_in_array(char **array, char *str)
251 for(i=0; array[i]; ++i)
252 if(!strcmp(str, array[i]))
258 Frees an array of strings.
260 void free_string_array(char **array)
265 for(i=0; array[i]; ++i)
271 Checks if a partition identified by a sysfs path is on a removable device.
273 int is_removable(char *devpath)
280 len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
281 /* Default to not removable if the path was too long. */
282 if(len+10>=(int)sizeof(fnbuf))
285 /* We got a partition as a parameter, but the removable property is on the
286 disk. Replace the last component with "removable". */
287 for(ptr=fnbuf+len; (ptr>fnbuf && *ptr!='/'); --ptr) ;
288 strcpy(ptr, "/removable");
290 fd = open(fnbuf, O_RDONLY);
299 printf(" Removable\n");
303 printf(" Not removable\n");
310 Checks if a partition's disk or any of its parent devices are connected to any
311 of a set of buses. The device is identified by a sysfs path. The bus array
312 must be terminated with a NULL entry.
314 int check_buses(char *devpath, char **buses)
320 len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
321 /* Default to no match if the path was too long. */
322 if(len+10>=(int)sizeof(fnbuf))
325 for(ptr=fnbuf+len; ptr>fnbuf+12; --ptr)
329 /* Replace the last component with "subsystem". */
330 strcpy(ptr, "/subsystem");
331 len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
336 /* Extract the last component of the subsystem symlink. */
337 for(; (len>0 && linkbuf[len-1]!='/'); --len) ;
342 printf(" Subsystem of %s is %s\n", fnbuf, linkbuf+len);
345 if(is_in_array(buses, linkbuf+len))
354 Check if an array of properties describes a device that can be mounted. An
355 array of explicitly allowed devices can be passed in as well. Both arrays must
356 be terminated by a NULL entry.
358 int can_mount(Property *props, char **allowed)
360 static char *removable_buses[] = { "usb", "firewire", 0 };
365 devname = get_property_value(props, "DEVNAME");
366 if(is_in_array(allowed, devname))
369 /* Special case for CD devices, since they are not partitions. Only allow
370 mounting if media is inserted. */
371 if(match_property_value(props, "ID_TYPE", "cd") && match_property_value(props, "ID_CDROM_MEDIA", "1"))
374 /* Only allow mounting partitions. */
375 if(!match_property_value(props, "DEVTYPE", "partition"))
378 devpath = get_property_value(props, "DEVPATH");
379 if(is_removable(devpath))
382 /* Certain buses are removable by nature, but devices only advertise
383 themselves as removable if they support removable media, e.g. memory card
385 bus = get_property_value(props, "ID_BUS");
386 if(is_in_array(removable_buses, bus))
389 return check_buses(devpath, removable_buses);
393 Returns an array of all device nodes in a directory. Symbolic links are
396 char **get_device_nodes(char *dirname)
405 char **checked = NULL;
409 dir = opendir(dirname);
413 while((de = readdir(dir)))
418 /* Ignore . and .. entries. */
419 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
422 snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dirname, de->d_name);
426 if(S_ISLNK(st.st_mode))
429 len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
437 /* There may be multiple symlinks to the same device. Only include each
438 device once in the returned array. */
441 for(i=0; (!duplicate && i<n_checked); ++i)
442 if(strcmp(node, checked[i])==0)
448 printf("Device %s is a duplicate\n", fnbuf);
452 checked = (char **)realloc(checked, (n_checked+1)*sizeof(char *));
453 checked[n_checked] = strdup(node);
456 nodes = (char **)realloc(nodes, (n_nodes+2)*sizeof(char *));
457 nodes[n_nodes] = strdup(fnbuf);
464 for(i=0; i<n_checked; ++i)
470 nodes[n_nodes] = NULL;
476 Reads the list of mounted devices from /etc/mtab and records the mount points.
478 void check_mounts(Device *devices)
483 file = setmntent("/etc/mtab", "r");
487 while((me = getmntent(file)))
491 for(i=0; devices[i].node; ++i)
492 if(!strcmp(devices[i].devname, me->mnt_fsname))
494 devices[i].mount_point = strdup(me->mnt_dir);
497 printf("Device %s is mounted on %s\n", devices[i].node, devices[i].mount_point);
505 Returns an array of all mountable devices.
507 Device *get_devices(void)
510 Device *devices = NULL;
512 char **mounted = NULL;
516 nodes = get_device_nodes("/dev/disk/by-id");
517 fstab = get_fstab_devices();
519 for(i=0; nodes[i]; ++i)
524 printf("Examining device %s\n", nodes[i]);
526 props = get_device_properties(nodes[i]);
530 printf(" No properties\n");
537 for(j=0; props[j].name; ++j)
538 printf(" %s = %s\n", props[j].name, props[j].value);
541 if(can_mount(props, fstab))
552 printf(" Using device\n");
554 devname = get_property_value(props, "DEVNAME");
556 /* Get a human-readable label for the device. Use filesystem label,
557 filesystem UUID or device node name in order of preference. */
558 label = get_property_value(props, "ID_FS_LABEL");
560 label = get_property_value(props, "ID_FS_UUID");
566 for(ptr=label; *ptr; ++ptr)
571 vendor = get_property_value(props, "ID_VENDOR");
572 model = get_property_value(props, "ID_MODEL");
574 pos = snprintf(buf, sizeof(buf), "%s", label);
576 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
580 /* Reserve space for a sentinel entry. */
581 devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
582 devices[n_devices].node = nodes[i];
583 devices[n_devices].devname = strdup(devname);
584 devices[n_devices].label = strdup(label);
585 devices[n_devices].description = strdup(buf);
586 devices[n_devices].mount_point = NULL;
587 devices[n_devices].time = st.st_mtime;
592 free_properties(props);
596 free_string_array(mounted);
600 /* Terminate the array with NULL pointers. */
601 devices[n_devices].node = NULL;
602 devices[n_devices].devname = NULL;
603 devices[n_devices].label = NULL;
604 devices[n_devices].description = NULL;
605 devices[n_devices].mount_point = NULL;
607 check_mounts(devices);
614 Frees an array of devices and all strings contained in it.
616 void free_devices(Device *devices)
621 for(i=0; devices[i].node; ++i)
623 free(devices[i].node);
624 free(devices[i].devname);
625 free(devices[i].label);
626 free(devices[i].description);
627 if(devices[i].mount_point)
628 free(devices[i].mount_point);
634 Mounts a device if it was not mounted, or unmounts if it was.
636 int toggle_device(Device *device, char *out_buf, int out_size)
638 int umount = !!device->mount_point;
639 char mount_point[1024];
643 struct timeval timeout;
650 /* Find a mount point that does not exist yet. */
655 len = snprintf(mount_point, sizeof(mount_point), "/media/%s", device->label);
656 if(len+2>=(int)sizeof(mount_point))
660 len += snprintf(mount_point+len, sizeof(mount_point)-len, "_%c", suffix);
662 if(access(mount_point, F_OK)<0 && errno==ENOENT)
680 /* Complete construction of pmount call */
683 pmount_argv[0] = "pmount";
686 while(pmount_argv[++last]);
688 pmount_argv[last] = device->node;
689 pmount_argv[last+1] = mount_point+7;
690 pmount_argv[last+2] = NULL;
695 printf("Running pumount %s\n", device->node);
699 printf("Running pmount");
700 while(pmount_argv[++i])
701 printf(" %s", pmount_argv[i]);
711 execl("/usr/bin/pumount", "pumount", device->node, NULL);
713 execvp("/usr/bin/pmount", pmount_argv);
723 FD_SET(pipe_fd[0], &fds);
725 timeout.tv_usec = 200000;
729 /* The write fd for the pipe may be inherited by a fuse server
730 process and stay open indefinitely. */
731 if(select(pipe_fd[0]+1, &fds, NULL, NULL, &timeout))
735 len = read(pipe_fd[0], out_buf+pos, out_size-pos-1);
740 else if(waitpid(pid, &status, 0))
749 waitpid(pid, &status, 0);
755 if(WIFEXITED(status))
757 if(WEXITSTATUS(status))
758 printf("Command exited successfully\n");
760 printf("Command exited with status %d\n", WEXITSTATUS(status));
762 else if(WIFSIGNALED(status))
763 printf("Command terminated with signal %d\n", WTERMSIG(status));
765 printf("Command exited with unknown result %04X\n", status);
768 if(!WIFEXITED(status) || WEXITSTATUS(status))
773 free(device->mount_point);
774 device->mount_point = NULL;
777 device->mount_point = strdup(mount_point);
783 Callback for activating a row in the device list. Mounts or unmounts the
784 device depending on operating mode.
786 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
795 model = gtk_tree_view_get_model(list);
797 if(!gtk_tree_model_get_iter(model, &iter, path))
800 gtk_tree_model_get(model, &iter, 1, &device, -1);
801 ret = toggle_device(device, output, sizeof(output));
806 /* Pmount terminated with nonzero status or a signal. Display an
807 error to the user. */
808 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", output);
809 g_signal_connect(dialog, "response", >k_main_quit, NULL);
810 gtk_widget_show_all(dialog);
816 if(post_mount_command && device->mount_point)
819 printf("Running %s in %s\n", post_mount_command, device->mount_point);
824 chdir(device->mount_point);
825 execlp(post_mount_command, post_mount_command, NULL);
835 Callback for the mount/unmount button. Causes the selected row in the device
836 list to be activated.
838 void button_clicked(GtkButton *button, gpointer user_data)
840 GtkWidget *list = (GtkWidget *)user_data;
841 GtkTreeSelection *selection;
846 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
847 gtk_tree_selection_get_selected(selection, &model, &iter);
848 path = gtk_tree_model_get_path(model, &iter);
849 gtk_tree_view_row_activated(GTK_TREE_VIEW(list), path, gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0));
850 gtk_tree_path_free(path);
856 Global key press callback for the window.
858 gboolean key_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
860 if(event->key.keyval==GDK_KEY_Escape)
874 printf("pmount-gui\n"
875 "Copyright (c) 2011-2015 Mikko Rasa, Mikkosoft Productions\n\n"
876 "Usage: pmount-gui [-v] [-u] [-r <command>] [-h] [-- <pmount options>]\n\n"
878 " -v Increase verbosity\n"
879 " -u Unmount a device (default is mount)\n"
880 " -r Run a command after mounting\n"
881 " -h Display this help\n");
884 int main(int argc, char **argv)
891 GtkTreeSelection *selection;
901 gtk_init(&argc, &argv);
903 while((opt = getopt(argc, argv, "vur:h"))!=-1) switch(opt)
912 post_mount_command = optarg;
919 /* argc - optind equals to number of options after "--" */
920 /* One empty element at the beginning is for program name for execvp */
921 /* Three empty elements at the end are required options for pmount + NULL */
923 pmount_argv = malloc(sizeof(char *)*(argc-optind+4));
925 for(i=0; i<(argc-optind); ++i)
926 pmount_argv[i+1] = argv[optind+i];
927 pmount_argv[argc-optind+1] = NULL;
929 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
930 gtk_container_set_border_width(GTK_CONTAINER(window), 5);
931 g_signal_connect(window, "destroy", G_CALLBACK(>k_main_quit), NULL);
932 g_signal_connect(window, "key-press-event", G_CALLBACK(&key_press), NULL);
934 box = gtk_vbox_new(FALSE, 5);
935 gtk_container_add(GTK_CONTAINER(window), box);
937 viewport = gtk_viewport_new(NULL, NULL);
938 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
939 gtk_box_pack_start(GTK_BOX(box), viewport, TRUE, TRUE, 0);
941 list = gtk_tree_view_new();
942 gtk_container_add(GTK_CONTAINER(viewport), list);
943 g_signal_connect(list, "row-activated", G_CALLBACK(&row_activated), NULL);
945 store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
946 gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
947 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list),
948 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
950 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
952 button = gtk_button_new_with_label(umount ? "Unmount" : "Mount");
953 g_signal_connect(button, "clicked", G_CALLBACK(&button_clicked), list);
954 gtk_box_pack_start(GTK_BOX(box), button, FALSE, TRUE, 0);
956 devices = get_devices();
960 /* Populate the list with devices in appropriate state. */
962 for(i=0; devices[i].node; ++i)
963 if(!devices[i].mount_point==!umount)
965 gtk_list_store_append(store, &iter);
966 gtk_list_store_set(store, &iter, 0, devices[i].description, 1, &devices[i], -1);
967 if(devices[i].time>latest)
969 /* Pre-select the device that appeared on the system most recently. */
970 latest = devices[i].time;
971 gtk_tree_selection_select_iter(selection, &iter);
980 gtk_widget_show_all(window);
985 /* Don't show the main window if no devices were listed. */
986 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
987 "No devices to %s", (umount ? "unmount" : "mount"));
988 g_signal_connect(dialog, "response", G_CALLBACK(>k_main_quit), NULL);
989 gtk_widget_show_all(dialog);
994 free_devices(devices);