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