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