Support optical media devices
[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, "ID_TYPE", "cd") && match_property_value(props, "ID_CDROM_MEDIA", "1"))
323                 return 1;
324
325         if(!match_property_value(props, "DEVTYPE", "partition"))
326                 return 0;
327
328         devpath = get_property_value(props, "DEVPATH");
329         if(is_removable(devpath))
330                 return 1;
331
332         bus = get_property_value(props, "ID_BUS");
333         if(is_in_array(removable_buses, bus))
334                 return 1;
335
336         return check_buses(devpath, removable_buses);
337 }
338
339 char **get_device_nodes(char *dirname)
340 {
341         DIR *dir;
342         struct dirent *de;
343         char fnbuf[256];
344         char linkbuf[256];
345         struct stat st;
346         char **nodes = NULL;
347         int n_nodes = 0;
348         char **checked = NULL;
349         int n_checked = 0;
350         int i;
351
352         dir = opendir(dirname);
353         if(!dir)
354                 return NULL;
355
356         while((de = readdir(dir)))
357         {
358                 char *node;
359                 int duplicate = 0;
360
361                 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
362                         continue;
363
364                 snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dirname, de->d_name);
365
366                 node = fnbuf;
367                 lstat(fnbuf, &st);
368                 if(S_ISLNK(st.st_mode))
369                 {
370                         int len;
371                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
372                         if(len!=-1)
373                         {
374                                 linkbuf[len] = 0;
375                                 node = linkbuf;
376                         }
377                 }
378
379                 if(checked)
380                 {
381                         for(i=0; (!duplicate && i<n_checked); ++i)
382                                 if(strcmp(node, checked[i])==0)
383                                         duplicate = 1;
384                 }
385                 if(duplicate)
386                 {
387                         if(verbosity>=2)
388                                 printf("Device %s is a duplicate\n", fnbuf);
389                         continue;
390                 }
391
392                 checked = (char **)realloc(checked, (n_checked+1)*sizeof(char *));
393                 checked[n_checked] = strdup(node);
394                 ++n_checked;
395
396                 nodes = (char **)realloc(nodes, (n_nodes+2)*sizeof(char *));
397                 nodes[n_nodes] = strdup(fnbuf);
398                 ++n_nodes;
399         }
400
401         closedir(dir);
402         if(checked)
403         {
404                 for(i=0; i<n_checked; ++i)
405                         free(checked[i]);
406                 free(checked);
407         }
408
409         if(nodes)
410                 nodes[n_nodes] = NULL;
411
412         return nodes;
413 }
414
415 Device *get_devices(void)
416 {
417         char **nodes = NULL;
418         Device *devices = NULL;
419         int n_devices = 0;
420         char **mounted = NULL;
421         char **fstab = NULL;
422         int i;
423
424         nodes = get_device_nodes("/dev/disk/by-id");
425         mounted = get_mounted_devices();
426         fstab = get_fstab_devices();
427
428         for(i=0; nodes[i]; ++i)
429         {
430                 if(verbosity>=1)
431                         printf("Examining device %s\n", nodes[i]);
432
433                 Property *props = get_device_properties(nodes[i]);
434                 if(!props)
435                 {
436                         if(verbosity>=2)
437                                 printf("  No properties\n");
438                         continue;
439                 }
440
441                 if(verbosity>=2)
442                 {
443                         int j;
444                         for(j=0; props[j].name; ++j)
445                                 printf("  %s = %s\n", props[j].name, props[j].value);
446                 }
447
448                 if(can_mount(props, fstab))
449                 {
450                         char *devname;
451                         char *label;
452                         char *vendor;
453                         char *model;
454                         char buf[256];
455                         char pos;
456                         struct stat st;
457
458                         if(verbosity>=1)
459                                 printf("  Using device\n");
460
461                         devname = get_property_value(props, "DEVNAME");
462
463                         label = get_property_value(props, "ID_FS_LABEL");
464                         if(!label)
465                                 label = get_property_value(props, "ID_FS_UUID");
466                         if(!label)
467                         {
468                                 char *ptr;
469
470                                 label = devname;
471                                 for(ptr=label; *ptr; ++ptr)
472                                         if(*ptr=='/')
473                                                 label = ptr+1;
474                         }
475
476                         vendor = get_property_value(props, "ID_VENDOR");
477                         model = get_property_value(props, "ID_MODEL");
478
479                         pos = snprintf(buf, sizeof(buf), "%s", label);
480                         if(vendor && model)
481                                 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
482
483                         stat(nodes[i], &st);
484
485                         devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
486                         devices[n_devices].node = nodes[i];
487                         devices[n_devices].label = strdup(label);
488                         devices[n_devices].description = strdup(buf);
489                         devices[n_devices].mounted = is_in_array(mounted, devname);
490                         devices[n_devices].time = st.st_mtime;
491                         ++n_devices;
492                 }
493                 else
494                         free(nodes[i]);
495                 free_properties(props);
496         }
497
498         free(nodes);
499         free_device_names(mounted);
500
501         if(devices)
502         {
503                 devices[n_devices].node = NULL;
504                 devices[n_devices].label = NULL;
505                 devices[n_devices].description = NULL;
506         }
507
508         return devices;
509 }
510
511 void free_devices(Device *devices)
512 {
513         int i;
514         if(!devices)
515                 return;
516         for(i=0; devices[i].node; ++i)
517         {
518                 free(devices[i].node);
519                 free(devices[i].label);
520                 free(devices[i].description);
521         }
522         free(devices);
523 }
524
525 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
526 {
527         GtkTreeModel *model;
528         GtkTreeIter iter;
529         int umount = *(int *)user_data;
530
531         model = gtk_tree_view_get_model(list);
532
533         if(gtk_tree_model_get_iter(model, &iter, path))
534         {
535                 Device *device;
536                 int pid;
537                 int pipe_fd[2];
538
539                 gtk_tree_model_get(model, &iter, 1, &device, -1);
540
541                 pipe(pipe_fd);
542
543                 pid = fork();
544                 if(pid==0)
545                 {
546                         if(verbosity>=1)
547                         {
548                                 if(umount)
549                                         printf("Running pumount %s\n", device->node);
550                                 else
551                                         printf("Running pmount %s %s\n", device->node, device->label);
552                         }
553
554                         close(pipe_fd[0]);
555                         dup2(pipe_fd[1], 1);
556                         dup2(pipe_fd[1], 2);
557
558                         if(umount)
559                                 execl("/usr/bin/pumount", "pumount", device->node, NULL);
560                         else
561                                 execl("/usr/bin/pmount", "pmount", device->node, device->label, NULL);
562                         _exit(1);
563                 }
564                 else if(pid>0)
565                 {
566                         char buf[1024];
567                         int pos = 0;
568                         int status;
569
570                         close(pipe_fd[1]);
571
572                         while(1)
573                         {
574                                 int len;
575
576                                 len = read(pipe_fd[0], buf+pos, sizeof(buf)-pos-1);
577                                 if(len<=0)
578                                         break;
579                                 pos += len;
580                         }
581
582                         buf[pos] = 0;
583
584                         waitpid(pid, &status, 0);
585                         if(!WIFEXITED(status) || WEXITSTATUS(status))
586                         {
587                                 GtkWidget *dialog;
588
589                                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf);
590                                 g_signal_connect(dialog, "response", &gtk_main_quit, NULL);
591                                 gtk_widget_show_all(dialog);
592                         }
593                         else
594                                 gtk_main_quit();
595                 }
596                 else
597                 {
598                 }
599         }
600
601         (void)column;
602 }
603
604 gboolean key_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
605 {
606         if(event->key.keyval==GDK_KEY_Escape)
607         {
608                 gtk_main_quit();
609                 return TRUE;
610         }
611
612         (void)widget;
613         (void)user_data;
614
615         return FALSE;
616 }
617
618 int main(int argc, char **argv)
619 {
620         GtkWidget *window;
621         GtkWidget *viewport;
622         GtkWidget *list;
623         GtkListStore *store;
624         GtkTreeSelection *selection;
625         GtkTreeIter iter;
626         Device *devices;
627         int i;
628         time_t latest;
629         int opt;
630         int umount = 0;
631         int n_listed;
632
633         gtk_init(&argc, &argv);
634
635         while((opt = getopt(argc, argv, "vu"))!=-1) switch(opt)
636         {
637         case 'v':
638                 ++verbosity;
639                 break;
640         case 'u':
641                 umount = 1;
642                 break;
643         }
644
645         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
646         gtk_container_set_border_width(GTK_CONTAINER(window), 5);
647         g_signal_connect(window, "destroy", G_CALLBACK(&gtk_main_quit), NULL);
648         g_signal_connect(window, "key-press-event", G_CALLBACK(&key_press), NULL);
649
650         viewport = gtk_viewport_new(NULL, NULL);
651         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
652         gtk_container_add(GTK_CONTAINER(window), viewport);
653
654         list = gtk_tree_view_new();
655         gtk_container_add(GTK_CONTAINER(viewport), list);
656         g_signal_connect(list, "row-activated", G_CALLBACK(&row_activated), &umount);
657
658         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
659         gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
660         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list),
661                 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
662
663         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
664
665         devices = get_devices();
666         n_listed = 0;
667         if(devices)
668         {
669                 latest = 0;
670                 for(i=0; devices[i].node; ++i)
671                         if(!devices[i].mounted==!umount)
672                         {
673                                 gtk_list_store_append(store, &iter);
674                                 gtk_list_store_set(store, &iter, 0, devices[i].description, 1, &devices[i], -1);
675                                 if(devices[i].time>latest)
676                                 {
677                                         latest = devices[i].time;
678                                         gtk_tree_selection_select_iter(selection, &iter);
679                                 }
680
681                                 ++n_listed;
682                         }
683
684         }
685
686         if(n_listed)
687                 gtk_widget_show_all(window);
688         else
689         {
690                 GtkWidget *dialog;
691
692                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
693                         "No devices to %s", (umount ? "unmount" : "mount"));
694                 g_signal_connect(dialog, "response", G_CALLBACK(&gtk_main_quit), NULL);
695                 gtk_widget_show_all(dialog);
696         }
697
698         gtk_main();
699
700         free_devices(devices);
701
702         return 0;
703 }