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