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