Minor refactoring
[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 <sys/stat.h>
11 #include <sys/wait.h>
12 #include <sys/select.h>
13 #include <gtk/gtk.h>
14 #include <gdk/gdkkeysyms.h>
15
16 typedef struct sProperty
17 {
18         char *name;
19         char *value;
20 } Property;
21
22 typedef struct sDevice
23 {
24         char *node;
25         char *label;
26         char *description;
27         int mounted;
28         time_t time;
29 } Device;
30
31 int verbosity = 0;
32
33 int parse_property(char *str, int size, Property *prop)
34 {
35         int equals = -1;
36         int i;
37
38         for(i=0; (equals<0 && i<size); ++i)
39                 if(str[i]=='=')
40                         equals = i;
41
42         if(equals<0)
43                 return -1;
44
45         prop->name = malloc(equals+1);
46         strncpy(prop->name, str, equals);
47         prop->name[equals] = 0;
48
49         prop->value = malloc(size-equals);
50         strncpy(prop->value, str+equals+1, size-equals-1);
51         prop->value[size-equals-1] = 0;
52
53         return 0;
54 }
55
56 Property *get_device_properties(char *node)
57 {
58         int pid;
59         int pipe_fd[2];
60
61         pipe(pipe_fd);
62
63         pid = fork();
64         if(pid==0)
65         {
66                 if(verbosity>=2)
67                         printf("Running udevadm info -q property -n \"%s\"\n", node);
68
69                 close(pipe_fd[0]);
70                 dup2(pipe_fd[1], 1);
71
72                 execl("/sbin/udevadm", "udevadm", "info", "-q", "property", "-n", node, NULL);
73                 _exit(1);
74         }
75         else if(pid>0)
76         {
77                 char *buf;
78                 int bufsize;
79                 int pos = 0;
80                 int eof = 0;
81                 Property *props = NULL;
82                 int n_props = 0;
83
84                 close(pipe_fd[1]);
85
86                 bufsize = 256;
87                 buf = (char *)malloc(bufsize);
88
89                 while(1)
90                 {
91                         int newline;
92                         int i;
93                         Property prop;
94
95                         if(!eof)
96                         {
97                                 int len;
98
99                                 len = read(pipe_fd[0], buf+pos, bufsize-pos);
100                                 if(len==0)
101                                         eof = 1;
102                                 else if(len==-1)
103                                         break;
104                                 pos += len;
105                         }
106
107                         newline = -1;
108                         for(i=0; (newline<0 && i<pos); ++i)
109                                 if(buf[i]=='\n')
110                                         newline = i;
111
112                         if(newline<0)
113                         {
114                                 if(eof)
115                                         break;
116                                 bufsize *= 2;
117                                 buf = (char *)realloc(buf, bufsize);
118                                 continue;
119                         }
120
121                         if(parse_property(buf, newline, &prop)==0)
122                         {
123                                 props = (Property *)realloc(props, (n_props+2)*sizeof(Property));
124                                 props[n_props] = prop;
125                                 ++n_props;
126
127                                 memmove(buf, buf+newline+1, pos-newline-1);
128                                 pos -= newline+1;
129                         }
130                         else
131                                 break;
132                 }
133
134                 free(buf);
135
136                 if(props)
137                 {
138                         props[n_props].name = NULL;
139                         props[n_props].value = NULL;
140                 }
141
142                 waitpid(pid, NULL, 0);
143                 close(pipe_fd[0]);
144
145                 return props;
146         }
147         else
148         {
149                 close(pipe_fd[0]);
150                 close(pipe_fd[1]);
151
152                 return NULL;
153         }
154 }
155
156 char *get_property_value(Property *props, char *name)
157 {
158         int i;
159         for(i=0; props[i].name; ++i)
160                 if(strcmp(props[i].name, name)==0)
161                         return props[i].value;
162         return NULL;
163 }
164
165 int match_property_value(Property *props, char *name, char *value)
166 {
167         char *v = get_property_value(props, name);
168         if(!v)
169                 return value==NULL;
170         return strcmp(v, value)==0;
171 }
172
173 void free_properties(Property *props)
174 {
175         int i;
176         if(!props)
177                 return;
178         for(i=0; props[i].name; ++i)
179         {
180                 free(props[i].name);
181                 free(props[i].value);
182         }
183         free(props);
184 }
185
186 char **get_mount_entries(char *filename, int (*predicate)(struct mntent *))
187 {
188         FILE *file;
189         struct mntent *me;
190         char **devices = NULL;
191         int n_devices = 0;
192
193         file = setmntent(filename, "r");
194         if(!file)
195                 return NULL;
196
197         while((me = getmntent(file)))
198                 if(!predicate || predicate(me))
199                 {
200                         devices = (char **)realloc(devices, (n_devices+2)*sizeof(char *));
201                         devices[n_devices] = strdup(me->mnt_fsname);
202                         ++n_devices;
203                 }
204
205         endmntent(file);
206         if(devices)
207                 devices[n_devices] = NULL;
208
209         return devices;
210 }
211
212 char **get_mounted_devices(void)
213 {
214         return get_mount_entries("/etc/mtab", NULL);
215 }
216
217 int is_user_mountable(struct mntent *me)
218 {
219         return hasmntopt(me, "user")!=NULL;
220 }
221
222 char **get_fstab_devices(void)
223 {
224         return get_mount_entries("/etc/fstab", &is_user_mountable);
225 }
226
227 int is_in_array(char **array, char *str)
228 {
229         int i;
230         if(!array || !str)
231                 return 0;
232         for(i=0; array[i]; ++i)
233                 if(!strcmp(str, array[i]))
234                         return 1;
235         return 0;
236 }
237
238 void free_string_array(char **array)
239 {
240         int i;
241         if(!array)
242                 return;
243         for(i=0; array[i]; ++i)
244                 free(array[i]);
245         free(array);
246 }
247
248 int is_removable(char *devpath)
249 {
250         char fnbuf[256];
251         int len;
252         char *ptr;
253         int fd;
254
255         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
256         if(len+10>=(int)sizeof(fnbuf))
257                 return 0;
258
259         for(ptr=fnbuf+len; (ptr>fnbuf && *ptr!='/'); --ptr) ;
260         strcpy(ptr, "/removable");
261         fd = open(fnbuf, O_RDONLY);
262         if(fd!=-1)
263         {
264                 char c;
265                 read(fd, &c, 1);
266                 close(fd);
267                 if(c=='1')
268                 {
269                         if(verbosity>=2)
270                                 printf("  Removable\n");
271                         return 1;
272                 }
273                 if(verbosity>=2)
274                         printf("  Not removable\n");
275         }
276
277         return 0;
278 }
279
280 int check_buses(char *devpath, char **buses)
281 {
282         char fnbuf[256];
283         char *ptr;
284         int len;
285
286         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
287         if(len+10>=(int)sizeof(fnbuf))
288                 return 0;
289
290         for(ptr=fnbuf+len; ptr>fnbuf+12; --ptr)
291                 if(*ptr=='/')
292                 {
293                         char linkbuf[256];
294                         strcpy(ptr, "/subsystem");
295                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
296
297                         if(len!=-1)
298                         {
299                                 linkbuf[len] = 0;
300                                 for(; (len>0 && linkbuf[len-1]!='/'); --len) ;
301                                 if(verbosity>=2)
302                                 {
303                                         *ptr = 0;
304                                         printf("  Subsystem of %s is %s\n", fnbuf, linkbuf+len);
305                                 }
306                                 if(is_in_array(buses, linkbuf+len))
307                                         return 1;
308                         }
309                 }
310
311         return 0;
312 }
313
314 int can_mount(Property *props, char **allowed)
315 {
316         static char *removable_buses[] = { "usb", "firewire", 0 };
317         char *devname;
318         char *devpath;
319         char *bus;
320
321         devname = get_property_value(props, "DEVNAME");
322         if(is_in_array(allowed, devname))
323                 return 1;
324
325         if(match_property_value(props, "ID_TYPE", "cd") && match_property_value(props, "ID_CDROM_MEDIA", "1"))
326                 return 1;
327
328         if(!match_property_value(props, "DEVTYPE", "partition"))
329                 return 0;
330
331         devpath = get_property_value(props, "DEVPATH");
332         if(is_removable(devpath))
333                 return 1;
334
335         bus = get_property_value(props, "ID_BUS");
336         if(is_in_array(removable_buses, bus))
337                 return 1;
338
339         return check_buses(devpath, removable_buses);
340 }
341
342 char **get_device_nodes(char *dirname)
343 {
344         DIR *dir;
345         struct dirent *de;
346         char fnbuf[256];
347         char linkbuf[256];
348         struct stat st;
349         char **nodes = NULL;
350         int n_nodes = 0;
351         char **checked = NULL;
352         int n_checked = 0;
353         int i;
354
355         dir = opendir(dirname);
356         if(!dir)
357                 return NULL;
358
359         while((de = readdir(dir)))
360         {
361                 char *node;
362                 int duplicate = 0;
363
364                 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
365                         continue;
366
367                 snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dirname, de->d_name);
368
369                 node = fnbuf;
370                 lstat(fnbuf, &st);
371                 if(S_ISLNK(st.st_mode))
372                 {
373                         int len;
374                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
375                         if(len!=-1)
376                         {
377                                 linkbuf[len] = 0;
378                                 node = linkbuf;
379                         }
380                 }
381
382                 if(checked)
383                 {
384                         for(i=0; (!duplicate && i<n_checked); ++i)
385                                 if(strcmp(node, checked[i])==0)
386                                         duplicate = 1;
387                 }
388                 if(duplicate)
389                 {
390                         if(verbosity>=2)
391                                 printf("Device %s is a duplicate\n", fnbuf);
392                         continue;
393                 }
394
395                 checked = (char **)realloc(checked, (n_checked+1)*sizeof(char *));
396                 checked[n_checked] = strdup(node);
397                 ++n_checked;
398
399                 nodes = (char **)realloc(nodes, (n_nodes+2)*sizeof(char *));
400                 nodes[n_nodes] = strdup(fnbuf);
401                 ++n_nodes;
402         }
403
404         closedir(dir);
405         if(checked)
406         {
407                 for(i=0; i<n_checked; ++i)
408                         free(checked[i]);
409                 free(checked);
410         }
411
412         if(nodes)
413                 nodes[n_nodes] = NULL;
414
415         return nodes;
416 }
417
418 Device *get_devices(void)
419 {
420         char **nodes = NULL;
421         Device *devices = NULL;
422         int n_devices = 0;
423         char **mounted = NULL;
424         char **fstab = NULL;
425         int i;
426
427         nodes = get_device_nodes("/dev/disk/by-id");
428         mounted = get_mounted_devices();
429         fstab = get_fstab_devices();
430
431         for(i=0; nodes[i]; ++i)
432         {
433                 Property *props;
434
435                 if(verbosity>=1)
436                         printf("Examining device %s\n", nodes[i]);
437
438                 props = get_device_properties(nodes[i]);
439                 if(!props)
440                 {
441                         if(verbosity>=2)
442                                 printf("  No properties\n");
443                         continue;
444                 }
445
446                 if(verbosity>=2)
447                 {
448                         int j;
449                         for(j=0; props[j].name; ++j)
450                                 printf("  %s = %s\n", props[j].name, props[j].value);
451                 }
452
453                 if(can_mount(props, fstab))
454                 {
455                         char *devname;
456                         char *label;
457                         char *vendor;
458                         char *model;
459                         char buf[256];
460                         char pos;
461                         struct stat st;
462
463                         if(verbosity>=1)
464                                 printf("  Using device\n");
465
466                         devname = get_property_value(props, "DEVNAME");
467
468                         label = get_property_value(props, "ID_FS_LABEL");
469                         if(!label)
470                                 label = get_property_value(props, "ID_FS_UUID");
471                         if(!label)
472                         {
473                                 char *ptr;
474
475                                 label = devname;
476                                 for(ptr=label; *ptr; ++ptr)
477                                         if(*ptr=='/')
478                                                 label = ptr+1;
479                         }
480
481                         vendor = get_property_value(props, "ID_VENDOR");
482                         model = get_property_value(props, "ID_MODEL");
483
484                         pos = snprintf(buf, sizeof(buf), "%s", label);
485                         if(vendor && model)
486                                 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
487
488                         stat(nodes[i], &st);
489
490                         devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
491                         devices[n_devices].node = nodes[i];
492                         devices[n_devices].label = strdup(label);
493                         devices[n_devices].description = strdup(buf);
494                         devices[n_devices].mounted = is_in_array(mounted, devname);
495                         devices[n_devices].time = st.st_mtime;
496                         ++n_devices;
497                 }
498                 else
499                         free(nodes[i]);
500                 free_properties(props);
501         }
502
503         free(nodes);
504         free_string_array(mounted);
505
506         if(devices)
507         {
508                 devices[n_devices].node = NULL;
509                 devices[n_devices].label = NULL;
510                 devices[n_devices].description = NULL;
511         }
512
513         return devices;
514 }
515
516 void free_devices(Device *devices)
517 {
518         int i;
519         if(!devices)
520                 return;
521         for(i=0; devices[i].node; ++i)
522         {
523                 free(devices[i].node);
524                 free(devices[i].label);
525                 free(devices[i].description);
526         }
527         free(devices);
528 }
529
530 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
531 {
532         GtkTreeModel *model;
533         GtkTreeIter iter;
534         int umount = *(int *)user_data;
535
536         model = gtk_tree_view_get_model(list);
537
538         if(gtk_tree_model_get_iter(model, &iter, path))
539         {
540                 Device *device;
541                 int pid;
542                 int pipe_fd[2];
543
544                 gtk_tree_model_get(model, &iter, 1, &device, -1);
545
546                 pipe(pipe_fd);
547
548                 pid = fork();
549                 if(pid==0)
550                 {
551                         if(verbosity>=1)
552                         {
553                                 if(umount)
554                                         printf("Running pumount %s\n", device->node);
555                                 else
556                                         printf("Running pmount %s %s\n", device->node, device->label);
557                         }
558
559                         close(pipe_fd[0]);
560                         dup2(pipe_fd[1], 1);
561                         dup2(pipe_fd[1], 2);
562
563                         if(umount)
564                                 execl("/usr/bin/pumount", "pumount", device->node, NULL);
565                         else
566                                 execl("/usr/bin/pmount", "pmount", device->node, device->label, NULL);
567                         _exit(1);
568                 }
569                 else if(pid>0)
570                 {
571                         char buf[1024];
572                         int pos = 0;
573                         int status = 0;
574                         fd_set fds;
575                         struct timeval timeout;
576
577                         close(pipe_fd[1]);
578                         FD_ZERO(&fds);
579                         FD_SET(pipe_fd[0], &fds);
580                         timeout.tv_sec = 0;
581                         timeout.tv_usec = 200000;
582
583                         while(1)
584                         {
585                                 if(select(pipe_fd[0]+1, &fds, NULL, NULL, &timeout))
586                                 {
587                                         int len;
588
589                                         len = read(pipe_fd[0], buf+pos, sizeof(buf)-pos-1);
590                                         if(len<=0)
591                                                 break;
592                                         pos += len;
593                                 }
594                                 else if(waitpid(pid, &status, 0))
595                                 {
596                                         pid = 0;
597                                         break;
598                                 }
599                         }
600
601                         if(pid)
602                                 waitpid(pid, &status, 0);
603
604                         buf[pos] = 0;
605
606                         if(verbosity>=1)
607                         {
608                                 if(WIFEXITED(status))
609                                 {
610                                         if(WEXITSTATUS(status))
611                                                 printf("Command exited successfully\n");
612                                         else
613                                                 printf("Command exited with status %d\n", WEXITSTATUS(status));
614                                 }
615                                 else if(WIFSIGNALED(status))
616                                         printf("Command terminated with signal %d\n", WTERMSIG(status));
617                                 else
618                                         printf("Command exited with unknown result %04X\n", status);
619                         }
620
621                         if(!WIFEXITED(status) || WEXITSTATUS(status))
622                         {
623                                 GtkWidget *dialog;
624
625                                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf);
626                                 g_signal_connect(dialog, "response", &gtk_main_quit, NULL);
627                                 gtk_widget_show_all(dialog);
628                         }
629                         else
630                                 gtk_main_quit();
631                 }
632         }
633
634         (void)column;
635 }
636
637 void button_clicked(GtkButton *button, gpointer user_data)
638 {
639         GtkWidget *list = (GtkWidget *)user_data;
640         GtkTreeSelection *selection;
641         GtkTreeIter iter;
642         GtkTreeModel *model;
643         GtkTreePath *path;
644
645         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
646         gtk_tree_selection_get_selected(selection, &model, &iter);
647         path = gtk_tree_model_get_path(model, &iter);
648         gtk_tree_view_row_activated(GTK_TREE_VIEW(list), path, gtk_tree_view_get_column(GTK_TREE_VIEW(list), 0));
649         gtk_tree_path_free(path);
650
651         (void)button;
652 }
653
654 gboolean key_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
655 {
656         if(event->key.keyval==GDK_KEY_Escape)
657         {
658                 gtk_main_quit();
659                 return TRUE;
660         }
661
662         (void)widget;
663         (void)user_data;
664
665         return FALSE;
666 }
667
668 int main(int argc, char **argv)
669 {
670         GtkWidget *window;
671         GtkWidget *box;
672         GtkWidget *viewport;
673         GtkWidget *list;
674         GtkListStore *store;
675         GtkTreeSelection *selection;
676         GtkWidget *button;
677         GtkTreeIter iter;
678         Device *devices;
679         int i;
680         time_t latest;
681         int opt;
682         int umount = 0;
683         int n_listed;
684
685         gtk_init(&argc, &argv);
686
687         while((opt = getopt(argc, argv, "vu"))!=-1) switch(opt)
688         {
689         case 'v':
690                 ++verbosity;
691                 break;
692         case 'u':
693                 umount = 1;
694                 break;
695         }
696
697         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
698         gtk_container_set_border_width(GTK_CONTAINER(window), 5);
699         g_signal_connect(window, "destroy", G_CALLBACK(&gtk_main_quit), NULL);
700         g_signal_connect(window, "key-press-event", G_CALLBACK(&key_press), NULL);
701
702         box = gtk_vbox_new(FALSE, 5);
703         gtk_container_add(GTK_CONTAINER(window), box);
704
705         viewport = gtk_viewport_new(NULL, NULL);
706         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
707         gtk_box_pack_start(GTK_BOX(box), viewport, TRUE, TRUE, 0);
708
709         list = gtk_tree_view_new();
710         gtk_container_add(GTK_CONTAINER(viewport), list);
711         g_signal_connect(list, "row-activated", G_CALLBACK(&row_activated), &umount);
712
713         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
714         gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
715         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list),
716                 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
717
718         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
719
720         button = gtk_button_new_with_label(umount ? "Unmount" : "Mount");
721         g_signal_connect(button, "clicked", G_CALLBACK(&button_clicked), list);
722         gtk_box_pack_start(GTK_BOX(box), button, FALSE, TRUE, 0);
723
724         devices = get_devices();
725         n_listed = 0;
726         if(devices)
727         {
728                 latest = 0;
729                 for(i=0; devices[i].node; ++i)
730                         if(!devices[i].mounted==!umount)
731                         {
732                                 gtk_list_store_append(store, &iter);
733                                 gtk_list_store_set(store, &iter, 0, devices[i].description, 1, &devices[i], -1);
734                                 if(devices[i].time>latest)
735                                 {
736                                         latest = devices[i].time;
737                                         gtk_tree_selection_select_iter(selection, &iter);
738                                 }
739
740                                 ++n_listed;
741                         }
742
743         }
744
745         if(n_listed)
746                 gtk_widget_show_all(window);
747         else
748         {
749                 GtkWidget *dialog;
750
751                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
752                         "No devices to %s", (umount ? "unmount" : "mount"));
753                 g_signal_connect(dialog, "response", G_CALLBACK(&gtk_main_quit), NULL);
754                 gtk_widget_show_all(dialog);
755         }
756
757         gtk_main();
758
759         free_devices(devices);
760
761         return 0;
762 }