]> git.tdb.fi Git - pmount-gui.git/blob - main.c
Generate a unique mount point name
[pmount-gui.git] / main.c
1 /* Required for strdup, snprintf, getopt */
2 #define _XOPEN_SOURCE 500
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <fcntl.h>
8 #include <dirent.h>
9 #include <mntent.h>
10 #include <errno.h>
11 #include <sys/stat.h>
12 #include <sys/wait.h>
13 #include <sys/select.h>
14 #include <gtk/gtk.h>
15 #include <gdk/gdkkeysyms.h>
16
17 typedef struct sProperty
18 {
19         char *name;
20         char *value;
21 } Property;
22
23 typedef struct sDevice
24 {
25         char *node;
26         char *devname;
27         char *label;
28         char *description;
29         char *mount_point;
30         time_t time;
31 } Device;
32
33 int verbosity = 0;
34 char *post_mount_command = NULL;
35
36 /**
37 Parses a string of the form name=value and places the components in a Property
38 structure.  Returns 0 on success, or -1 if the string wasn't a valid property.
39 */
40 int parse_property(char *str, int size, Property *prop)
41 {
42         int equals = -1;
43         int i;
44
45         for(i=0; (equals<0 && i<size); ++i)
46                 if(str[i]=='=')
47                         equals = i;
48
49         if(equals<0)
50                 return -1;
51
52         prop->name = malloc(equals+1);
53         strncpy(prop->name, str, equals);
54         prop->name[equals] = 0;
55
56         prop->value = malloc(size-equals);
57         strncpy(prop->value, str+equals+1, size-equals-1);
58         prop->value[size-equals-1] = 0;
59
60         return 0;
61 }
62
63 /**
64 Retrieves all properties associated with a /dev node.  The returned array is
65 terminated with an entry containing NULL values.  Use free_properties to free
66 the array.
67 */
68 Property *get_device_properties(char *node)
69 {
70         int pid;
71         int pipe_fd[2];
72         char *buf;
73         int bufsize;
74         int pos = 0;
75         int eof = 0;
76         Property *props = NULL;
77         int n_props = 0;
78
79         pipe(pipe_fd);
80
81         pid = fork();
82         if(pid==0)
83         {
84                 /* Child process */
85                 if(verbosity>=2)
86                         printf("Running udevadm info -q property -n \"%s\"\n", node);
87
88                 close(pipe_fd[0]);
89                 dup2(pipe_fd[1], 1);
90
91                 execl("/sbin/udevadm", "udevadm", "info", "-q", "property", "-n", node, NULL);
92                 _exit(1);
93         }
94         else if(pid<0)
95         {
96                 close(pipe_fd[0]);
97                 close(pipe_fd[1]);
98
99                 return NULL;
100         }
101
102         /* Parent process */
103         close(pipe_fd[1]);
104
105         bufsize = 256;
106         buf = (char *)malloc(bufsize);
107
108         while(1)
109         {
110                 int newline;
111                 int i;
112                 Property prop;
113
114                 if(!eof)
115                 {
116                         int len;
117
118                         len = read(pipe_fd[0], buf+pos, bufsize-pos);
119                         if(len==0)
120                                 eof = 1;
121                         else if(len==-1)
122                                 break;
123                         pos += len;
124                 }
125
126                 newline = -1;
127                 for(i=0; (newline<0 && i<pos); ++i)
128                         if(buf[i]=='\n')
129                                 newline = i;
130
131                 if(newline<0)
132                 {
133                         if(eof)
134                                 break;
135
136                         /* There was no newline in the buffer but there is more output to
137                         be read.  Try again with a larger buffer. */
138                         bufsize *= 2;
139                         buf = (char *)realloc(buf, bufsize);
140                         continue;
141                 }
142
143                 if(parse_property(buf, newline, &prop)==0)
144                 {
145                         /* Reserve space for a sentinel value as well. */
146                         props = (Property *)realloc(props, (n_props+2)*sizeof(Property));
147                         props[n_props] = prop;
148                         ++n_props;
149
150                         memmove(buf, buf+newline+1, pos-newline-1);
151                         pos -= newline+1;
152                 }
153                 else
154                         break;
155         }
156
157         free(buf);
158
159         if(props)
160         {
161                 /* Terminate the array with NULL pointers. */
162                 props[n_props].name = NULL;
163                 props[n_props].value = NULL;
164         }
165
166         waitpid(pid, NULL, 0);
167         close(pipe_fd[0]);
168
169         return props;
170 }
171
172 /**
173 Looks for a property in an array of properties and returns its value.  Returns
174 NULL if the property was not found.
175 */
176 char *get_property_value(Property *props, char *name)
177 {
178         int i;
179         for(i=0; props[i].name; ++i)
180                 if(strcmp(props[i].name, name)==0)
181                         return props[i].value;
182         return NULL;
183 }
184
185 /**
186 Checks if a property has a specific value.  A NULL value is matched if the
187 property does not exist.
188 */
189 int match_property_value(Property *props, char *name, char *value)
190 {
191         char *v = get_property_value(props, name);
192         if(!v)
193                 return value==NULL;
194         return strcmp(v, value)==0;
195 }
196
197 /**
198 Frees an array of properties and all strings contained in it.
199 */
200 void free_properties(Property *props)
201 {
202         int i;
203         if(!props)
204                 return;
205         for(i=0; props[i].name; ++i)
206         {
207                 free(props[i].name);
208                 free(props[i].value);
209         }
210         free(props);
211 }
212
213 /**
214 Returns an array of user-mountable devices listed in fstab.
215 */
216 char **get_fstab_devices(void)
217 {
218         FILE *file;
219         struct mntent *me;
220         char **devices = NULL;
221         int n_devices = 0;
222
223         file = setmntent("/etc/fstab", "r");
224         if(!file)
225                 return NULL;
226
227         while((me = getmntent(file)))
228                 if(hasmntopt(me, "user")!=NULL)
229                 {
230                         devices = (char **)realloc(devices, (n_devices+2)*sizeof(char *));
231                         devices[n_devices] = strdup(me->mnt_fsname);
232                         ++n_devices;
233                 }
234
235         endmntent(file);
236         if(devices)
237                 devices[n_devices] = NULL;
238
239         return devices;
240 }
241
242 /**
243 Checks if an array of strings contains the specified string.
244 */
245 int is_in_array(char **array, char *str)
246 {
247         int i;
248         if(!array || !str)
249                 return 0;
250         for(i=0; array[i]; ++i)
251                 if(!strcmp(str, array[i]))
252                         return 1;
253         return 0;
254 }
255
256 /**
257 Frees an array of strings.
258 */
259 void free_string_array(char **array)
260 {
261         int i;
262         if(!array)
263                 return;
264         for(i=0; array[i]; ++i)
265                 free(array[i]);
266         free(array);
267 }
268
269 /**
270 Checks if a partition identified by a sysfs path is on a removable device.
271 */
272 int is_removable(char *devpath)
273 {
274         char fnbuf[256];
275         int len;
276         char *ptr;
277         int fd;
278
279         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
280         /* Default to not removable if the path was too long. */
281         if(len+10>=(int)sizeof(fnbuf))
282                 return 0;
283
284         /* We got a partition as a parameter, but the removable property is on the
285         disk.  Replace the last component with "removable". */
286         for(ptr=fnbuf+len; (ptr>fnbuf && *ptr!='/'); --ptr) ;
287         strcpy(ptr, "/removable");
288
289         fd = open(fnbuf, O_RDONLY);
290         if(fd!=-1)
291         {
292                 char c;
293                 read(fd, &c, 1);
294                 close(fd);
295                 if(c=='1')
296                 {
297                         if(verbosity>=2)
298                                 printf("  Removable\n");
299                         return 1;
300                 }
301                 if(verbosity>=2)
302                         printf("  Not removable\n");
303         }
304
305         return 0;
306 }
307
308 /**
309 Checks if a partition's disk or any of its parent devices are connected to any
310 of a set of buses.  The device is identified by a sysfs path.  The bus array
311 must be terminated with a NULL entry.
312 */
313 int check_buses(char *devpath, char **buses)
314 {
315         char fnbuf[256];
316         char *ptr;
317         int len;
318
319         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
320         /* Default to no match if the path was too long. */
321         if(len+10>=(int)sizeof(fnbuf))
322                 return 0;
323
324         for(ptr=fnbuf+len; ptr>fnbuf+12; --ptr)
325                 if(*ptr=='/')
326                 {
327                         char linkbuf[256];
328                         /* Replace the last component with "subsystem". */
329                         strcpy(ptr, "/subsystem");
330                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
331
332                         if(len!=-1)
333                         {
334                                 linkbuf[len] = 0;
335                                 /* Extract the last component of the subsystem symlink. */
336                                 for(; (len>0 && linkbuf[len-1]!='/'); --len) ;
337
338                                 if(verbosity>=2)
339                                 {
340                                         *ptr = 0;
341                                         printf("  Subsystem of %s is %s\n", fnbuf, linkbuf+len);
342                                 }
343
344                                 if(is_in_array(buses, linkbuf+len))
345                                         return 1;
346                         }
347                 }
348
349         return 0;
350 }
351
352 /**
353 Check if an array of properties describes a device that can be mounted.  An
354 array of explicitly allowed devices can be passed in as well.  Both arrays must
355 be terminated by a NULL entry.
356 */
357 int can_mount(Property *props, char **allowed)
358 {
359         static char *removable_buses[] = { "usb", "firewire", 0 };
360         char *devname;
361         char *devpath;
362         char *bus;
363
364         devname = get_property_value(props, "DEVNAME");
365         if(is_in_array(allowed, devname))
366                 return 1;
367
368         /* Special case for CD devices, since they are not partitions.  Only allow
369         mounting if media is inserted. */
370         if(match_property_value(props, "ID_TYPE", "cd") && match_property_value(props, "ID_CDROM_MEDIA", "1"))
371                 return 1;
372
373         /* Only allow mounting partitions. */
374         if(!match_property_value(props, "DEVTYPE", "partition"))
375                 return 0;
376
377         devpath = get_property_value(props, "DEVPATH");
378         if(is_removable(devpath))
379                 return 1;
380
381         /* Certain buses are removable by nature, but devices only advertise
382         themselves as removable if they support removable media, e.g. memory card
383         readers. */
384         bus = get_property_value(props, "ID_BUS");
385         if(is_in_array(removable_buses, bus))
386                 return 1;
387
388         return check_buses(devpath, removable_buses);
389 }
390
391 /**
392 Returns an array of all device nodes in a directory.  Symbolic links are
393 dereferenced.
394 */
395 char **get_device_nodes(char *dirname)
396 {
397         DIR *dir;
398         struct dirent *de;
399         char fnbuf[256];
400         char linkbuf[256];
401         struct stat st;
402         char **nodes = NULL;
403         int n_nodes = 0;
404         char **checked = NULL;
405         int n_checked = 0;
406         int i;
407
408         dir = opendir(dirname);
409         if(!dir)
410                 return NULL;
411
412         while((de = readdir(dir)))
413         {
414                 char *node;
415                 int duplicate = 0;
416
417                 /* Ignore . and .. entries. */
418                 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
419                         continue;
420
421                 snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dirname, de->d_name);
422
423                 node = fnbuf;
424                 lstat(fnbuf, &st);
425                 if(S_ISLNK(st.st_mode))
426                 {
427                         int len;
428                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
429                         if(len!=-1)
430                         {
431                                 linkbuf[len] = 0;
432                                 node = linkbuf;
433                         }
434                 }
435
436                 /* There may be multiple symlinks to the same device.  Only include each
437                 device once in the returned array. */
438                 if(checked)
439                 {
440                         for(i=0; (!duplicate && i<n_checked); ++i)
441                                 if(strcmp(node, checked[i])==0)
442                                         duplicate = 1;
443                 }
444                 if(duplicate)
445                 {
446                         if(verbosity>=2)
447                                 printf("Device %s is a duplicate\n", fnbuf);
448                         continue;
449                 }
450
451                 checked = (char **)realloc(checked, (n_checked+1)*sizeof(char *));
452                 checked[n_checked] = strdup(node);
453                 ++n_checked;
454
455                 nodes = (char **)realloc(nodes, (n_nodes+2)*sizeof(char *));
456                 nodes[n_nodes] = strdup(fnbuf);
457                 ++n_nodes;
458         }
459
460         closedir(dir);
461         if(checked)
462         {
463                 for(i=0; i<n_checked; ++i)
464                         free(checked[i]);
465                 free(checked);
466         }
467
468         if(nodes)
469                 nodes[n_nodes] = NULL;
470
471         return nodes;
472 }
473
474 /**
475 Reads the list of mounted devices from /etc/mtab and records the mount points.
476 */
477 void check_mounts(Device *devices)
478 {
479         FILE *file;
480         struct mntent *me;
481
482         file = setmntent("/etc/mtab", "r");
483         if(!file)
484                 return;
485
486         while((me = getmntent(file)))
487         {
488                 int i;
489
490                 for(i=0; devices[i].node; ++i)
491                         if(!strcmp(devices[i].devname, me->mnt_fsname))
492                         {
493                                 devices[i].mount_point = strdup(me->mnt_dir);
494
495                                 if(verbosity>=1)
496                                         printf("Device %s is mounted on %s\n", devices[i].node, devices[i].mount_point);
497                         }
498         }
499
500         endmntent(file);
501 }
502
503 /**
504 Returns an array of all mountable devices.
505 */
506 Device *get_devices(void)
507 {
508         char **nodes = NULL;
509         Device *devices = NULL;
510         int n_devices = 0;
511         char **mounted = NULL;
512         char **fstab = NULL;
513         int i;
514
515         nodes = get_device_nodes("/dev/disk/by-id");
516         fstab = get_fstab_devices();
517
518         for(i=0; nodes[i]; ++i)
519         {
520                 Property *props;
521
522                 if(verbosity>=1)
523                         printf("Examining device %s\n", nodes[i]);
524
525                 props = get_device_properties(nodes[i]);
526                 if(!props)
527                 {
528                         if(verbosity>=2)
529                                 printf("  No properties\n");
530                         continue;
531                 }
532
533                 if(verbosity>=2)
534                 {
535                         int j;
536                         for(j=0; props[j].name; ++j)
537                                 printf("  %s = %s\n", props[j].name, props[j].value);
538                 }
539
540                 if(can_mount(props, fstab))
541                 {
542                         char *devname;
543                         char *label;
544                         char *vendor;
545                         char *model;
546                         char buf[256];
547                         char pos;
548                         struct stat st;
549
550                         if(verbosity>=1)
551                                 printf("  Using device\n");
552
553                         devname = get_property_value(props, "DEVNAME");
554
555                         /* Get a human-readable label for the device.  Use filesystem label,
556                         filesystem UUID or device node name in order of preference. */
557                         label = get_property_value(props, "ID_FS_LABEL");
558                         if(!label)
559                                 label = get_property_value(props, "ID_FS_UUID");
560                         if(!label)
561                         {
562                                 char *ptr;
563
564                                 label = devname;
565                                 for(ptr=label; *ptr; ++ptr)
566                                         if(*ptr=='/')
567                                                 label = ptr+1;
568                         }
569
570                         vendor = get_property_value(props, "ID_VENDOR");
571                         model = get_property_value(props, "ID_MODEL");
572
573                         pos = snprintf(buf, sizeof(buf), "%s", label);
574                         if(vendor && model)
575                                 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
576
577                         stat(nodes[i], &st);
578
579                         /* Reserve space for a sentinel entry. */
580                         devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
581                         devices[n_devices].node = nodes[i];
582                         devices[n_devices].devname = strdup(devname);
583                         devices[n_devices].label = strdup(label);
584                         devices[n_devices].description = strdup(buf);
585                         devices[n_devices].mount_point = NULL;
586                         devices[n_devices].time = st.st_mtime;
587                         ++n_devices;
588                 }
589                 else
590                         free(nodes[i]);
591                 free_properties(props);
592         }
593
594         free(nodes);
595         free_string_array(mounted);
596
597         if(devices)
598         {
599                 /* Terminate the array with NULL pointers. */
600                 devices[n_devices].node = NULL;
601                 devices[n_devices].devname = NULL;
602                 devices[n_devices].label = NULL;
603                 devices[n_devices].description = NULL;
604                 devices[n_devices].mount_point = NULL;
605
606                 check_mounts(devices);
607         }
608
609         return devices;
610 }
611
612 /**
613 Frees an array of devices and all strings contained in it.
614 */
615 void free_devices(Device *devices)
616 {
617         int i;
618         if(!devices)
619                 return;
620         for(i=0; devices[i].node; ++i)
621         {
622                 free(devices[i].node);
623                 free(devices[i].devname);
624                 free(devices[i].label);
625                 free(devices[i].description);
626                 if(devices[i].mount_point)
627                         free(devices[i].mount_point);
628         }
629         free(devices);
630 }
631
632 /**
633 Mounts a device if it was not mounted, or unmounts if it was.
634 */
635 int toggle_device(Device *device, char *out_buf, int out_size)
636 {
637         int umount = !!device->mount_point;
638         char mount_point[1024];
639         int pos = 0;
640         int status = 0;
641         fd_set fds;
642         struct timeval timeout;
643         int pid;
644         int pipe_fd[2];
645         char suffix = 0;
646
647         out_buf[0] = 0;
648
649         /* Find a mount point that does not exist yet. */
650         while(1)
651         {
652                 int len;
653
654                 len = snprintf(mount_point, sizeof(mount_point), "/media/%s", device->label);
655                 if(len+2>=(int)sizeof(mount_point))
656                         return -1;
657
658                 if(suffix)
659                         len += snprintf(mount_point+len, sizeof(mount_point)-len, "_%c", suffix);
660
661                 if(access(mount_point, F_OK)<0 && errno==ENOENT)
662                         break;
663
664                 if(suffix==0)
665                         suffix = '1';
666                 else if(suffix<'9')
667                         ++suffix;
668                 else
669                         return -1;
670         }
671
672         pipe(pipe_fd);
673
674         pid = fork();
675         if(pid==0)
676         {
677                 /* Child process */
678                 if(verbosity>=1)
679                 {
680                         if(umount)
681                                 printf("Running pumount %s\n", device->node);
682                         else
683                                 printf("Running pmount %s %s\n", device->node, mount_point+7);
684                 }
685
686                 close(pipe_fd[0]);
687                 dup2(pipe_fd[1], 1);
688                 dup2(pipe_fd[1], 2);
689
690                 if(umount)
691                         execl("/usr/bin/pumount", "pumount", device->node, NULL);
692                 else
693                         execl("/usr/bin/pmount", "pmount", device->node, mount_point+7, NULL);
694                 _exit(1);
695         }
696         else if(pid<0)
697                 return -1;
698
699         /* Parent process */
700
701         close(pipe_fd[1]);
702         FD_ZERO(&fds);
703         FD_SET(pipe_fd[0], &fds);
704         timeout.tv_sec = 0;
705         timeout.tv_usec = 200000;
706
707         while(1)
708         {
709                 /* The write fd for the pipe may be inherited by a fuse server
710                 process and stay open indefinitely. */
711                 if(select(pipe_fd[0]+1, &fds, NULL, NULL, &timeout))
712                 {
713                         int len;
714
715                         len = read(pipe_fd[0], out_buf+pos, out_size-pos-1);
716                         if(len<=0)
717                                 break;
718                         pos += len;
719                 }
720                 else if(waitpid(pid, &status, 0))
721                 {
722                         pid = 0;
723                         break;
724                 }
725         }
726
727         close(pipe_fd[0]);
728         if(pid)
729                 waitpid(pid, &status, 0);
730
731         out_buf[pos] = 0;
732
733         if(verbosity>=1)
734         {
735                 if(WIFEXITED(status))
736                 {
737                         if(WEXITSTATUS(status))
738                                 printf("Command exited successfully\n");
739                         else
740                                 printf("Command exited with status %d\n", WEXITSTATUS(status));
741                 }
742                 else if(WIFSIGNALED(status))
743                         printf("Command terminated with signal %d\n", WTERMSIG(status));
744                 else
745                         printf("Command exited with unknown result %04X\n", status);
746         }
747
748         if(!WIFEXITED(status) || WEXITSTATUS(status))
749                 return -1;
750
751         if(umount)
752         {
753                 free(device->mount_point);
754                 device->mount_point = NULL;
755         }
756         else
757                 device->mount_point = strdup(mount_point);
758
759         return 0;
760 }
761
762 /**
763 Callback for activating a row in the device list.  Mounts or unmounts the
764 device depending on operating mode.
765 */
766 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
767 {
768         GtkTreeModel *model;
769         GtkTreeIter iter;
770         Device *device;
771         int ret;
772         char output[1024];
773         int pid;
774
775         model = gtk_tree_view_get_model(list);
776
777         if(!gtk_tree_model_get_iter(model, &iter, path))
778                 return;
779
780         gtk_tree_model_get(model, &iter, 1, &device, -1);
781         ret = toggle_device(device, output, sizeof(output));
782         if(ret)
783         {
784                 GtkWidget *dialog;
785
786                 /* Pmount terminated with nonzero status or a signal.  Display an
787                 error to the user. */
788                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", output);
789                 g_signal_connect(dialog, "response", &gtk_main_quit, NULL);
790                 gtk_widget_show_all(dialog);
791                 return;
792         }
793
794         gtk_main_quit();
795
796         if(post_mount_command && device->mount_point)
797         {
798                 if(verbosity>=1)
799                         printf("Running %s in %s\n", post_mount_command, device->mount_point);
800
801                 pid = fork();
802                 if(pid==0)
803                 {
804                         chdir(device->mount_point);
805                         execlp(post_mount_command, post_mount_command, NULL);
806                         _exit(1);
807                 }
808         }
809
810         (void)column;
811         (void)user_data;
812 }
813
814 /**
815 Callback for the mount/unmount button.  Causes the selected row in the device
816 list to be activated.
817 */
818 void button_clicked(GtkButton *button, gpointer user_data)
819 {
820         GtkWidget *list = (GtkWidget *)user_data;
821         GtkTreeSelection *selection;
822         GtkTreeIter iter;
823         GtkTreeModel *model;
824         GtkTreePath *path;
825
826         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
827         gtk_tree_selection_get_selected(selection, &model, &iter);
828         path = gtk_tree_model_get_path(model, &iter);
829         gtk_tree_view_row_activated(GTK_TREE_VIEW(list), path, gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0));
830         gtk_tree_path_free(path);
831
832         (void)button;
833 }
834
835 /**
836 Global key press callback for the window.
837 */
838 gboolean key_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
839 {
840         if(event->key.keyval==GDK_KEY_Escape)
841         {
842                 gtk_main_quit();
843                 return TRUE;
844         }
845
846         (void)widget;
847         (void)user_data;
848
849         return FALSE;
850 }
851
852 void show_help(void)
853 {
854         printf("pmount-gui\n"
855                 "Copyright (c) 2011-2015 Mikko Rasa, Mikkosoft Productions\n\n"
856                 "Usage: pmount-gui [-v] [-u] [-r <command>] [-h]\n\n"
857                 "Options:\n"
858                 "  -v  Increase verbosity\n"
859                 "  -u  Unmount a device (default is mount)\n"
860                 "  -r  Run a command after mounting\n"
861                 "  -h  Display this help\n");
862 }
863
864 int main(int argc, char **argv)
865 {
866         GtkWidget *window;
867         GtkWidget *box;
868         GtkWidget *viewport;
869         GtkWidget *list;
870         GtkListStore *store;
871         GtkTreeSelection *selection;
872         GtkWidget *button;
873         GtkTreeIter iter;
874         Device *devices;
875         int i;
876         time_t latest;
877         int opt;
878         int umount = 0;
879         int n_listed;
880
881         gtk_init(&argc, &argv);
882
883         while((opt = getopt(argc, argv, "vur:h"))!=-1) switch(opt)
884         {
885         case 'v':
886                 ++verbosity;
887                 break;
888         case 'u':
889                 umount = 1;
890                 break;
891         case 'r':
892                 post_mount_command = optarg;
893                 break;
894         case 'h':
895                 show_help();
896                 return 0;
897         }
898
899         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
900         gtk_container_set_border_width(GTK_CONTAINER(window), 5);
901         g_signal_connect(window, "destroy", G_CALLBACK(&gtk_main_quit), NULL);
902         g_signal_connect(window, "key-press-event", G_CALLBACK(&key_press), NULL);
903
904         box = gtk_vbox_new(FALSE, 5);
905         gtk_container_add(GTK_CONTAINER(window), box);
906
907         viewport = gtk_viewport_new(NULL, NULL);
908         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
909         gtk_box_pack_start(GTK_BOX(box), viewport, TRUE, TRUE, 0);
910
911         list = gtk_tree_view_new();
912         gtk_container_add(GTK_CONTAINER(viewport), list);
913         g_signal_connect(list, "row-activated", G_CALLBACK(&row_activated), NULL);
914
915         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
916         gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
917         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list),
918                 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
919
920         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
921
922         button = gtk_button_new_with_label(umount ? "Unmount" : "Mount");
923         g_signal_connect(button, "clicked", G_CALLBACK(&button_clicked), list);
924         gtk_box_pack_start(GTK_BOX(box), button, FALSE, TRUE, 0);
925
926         devices = get_devices();
927         n_listed = 0;
928         if(devices)
929         {
930                 /* Populate the list with devices in appropriate state. */
931                 latest = 0;
932                 for(i=0; devices[i].node; ++i)
933                         if(!devices[i].mount_point==!umount)
934                         {
935                                 gtk_list_store_append(store, &iter);
936                                 gtk_list_store_set(store, &iter, 0, devices[i].description, 1, &devices[i], -1);
937                                 if(devices[i].time>latest)
938                                 {
939                                         /* Pre-select the device that appeared on the system most recently. */
940                                         latest = devices[i].time;
941                                         gtk_tree_selection_select_iter(selection, &iter);
942                                 }
943
944                                 ++n_listed;
945                         }
946
947         }
948
949         if(n_listed)
950                 gtk_widget_show_all(window);
951         else
952         {
953                 GtkWidget *dialog;
954
955                 /* Don't show the main window if no devices were listed. */
956                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
957                         "No devices to %s", (umount ? "unmount" : "mount"));
958                 g_signal_connect(dialog, "response", G_CALLBACK(&gtk_main_quit), NULL);
959                 gtk_widget_show_all(dialog);
960         }
961
962         gtk_main();
963
964         free_devices(devices);
965
966         return 0;
967 }