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