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