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