Generalize mtab handling code
[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_in_array(char **names, char *devname)
215 {
216         int i;
217         if(!names || !devname)
218                 return 0;
219         for(i=0; names[i]; ++i)
220                 if(!strcmp(devname, names[i]))
221                         return 1;
222         return 0;
223 }
224
225 void free_device_names(char **names)
226 {
227         int i;
228         if(!names)
229                 return;
230         for(i=0; names[i]; ++i)
231                 free(names[i]);
232         free(names);
233 }
234
235 int is_removable(char *devpath)
236 {
237         char fnbuf[256];
238         int len;
239         char *ptr;
240         int fd;
241
242         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
243         if(len+10>=(int)sizeof(fnbuf))
244                 return 0;
245
246         for(ptr=fnbuf+len; (ptr>fnbuf && *ptr!='/'); --ptr) ;
247         strcpy(ptr, "/removable");
248         fd = open(fnbuf, O_RDONLY);
249         if(fd!=-1)
250         {
251                 char c;
252                 read(fd, &c, 1);
253                 close(fd);
254                 if(c=='1')
255                 {
256                         if(verbosity>=2)
257                                 printf("  Removable\n");
258                         return 1;
259                 }
260                 if(verbosity>=2)
261                         printf("  Not removable\n");
262         }
263
264         return 0;
265 }
266
267 int check_buses(char *devpath, char **buses)
268 {
269         char fnbuf[256];
270         char *ptr;
271         int len;
272
273         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
274         if(len+10>=(int)sizeof(fnbuf))
275                 return 0;
276
277         for(ptr=fnbuf+len; ptr>fnbuf+12; --ptr)
278                 if(*ptr=='/')
279                 {
280                         char linkbuf[256];
281                         strcpy(ptr, "/subsystem");
282                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
283                         *ptr = 0;
284
285                         if(len!=-1)
286                         {
287                                 int i;
288                                 linkbuf[len] = 0;
289                                 for(; (len>0 && linkbuf[len-1]!='/'); --len) ;
290                                 if(verbosity>=2)
291                                         printf("  Subsystem of %s is %s\n", fnbuf, linkbuf+len);
292                                 for(i=0; buses[i]; ++i)
293                                         if(strcmp(linkbuf+len, buses[i])==0)
294                                                 return 1;
295                         }
296                 }
297
298         return 0;
299 }
300
301 int can_mount(Property *props)
302 {
303         static char *removable_buses[] = { "usb", "firewire", 0 };
304         char *devpath;
305         int i;
306
307         if(!match_property_value(props, "DEVTYPE", "partition"))
308                 return 0;
309
310         devpath = get_property_value(props, "DEVPATH");
311         if(is_removable(devpath))
312                 return 1;
313
314         for(i=0; removable_buses[i]; ++i)
315                 if(match_property_value(props, "ID_BUS", removable_buses[i]))
316                         return 1;
317
318         return check_buses(devpath, removable_buses);
319 }
320
321 char **get_device_nodes(char *dirname)
322 {
323         DIR *dir;
324         struct dirent *de;
325         char fnbuf[256];
326         char linkbuf[256];
327         struct stat st;
328         char **nodes = NULL;
329         int n_nodes = 0;
330         char **checked = NULL;
331         int n_checked = 0;
332         int i;
333
334         dir = opendir(dirname);
335         if(!dir)
336                 return NULL;
337
338         while((de = readdir(dir)))
339         {
340                 char *node;
341                 int duplicate = 0;
342
343                 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
344                         continue;
345
346                 snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dirname, de->d_name);
347
348                 node = fnbuf;
349                 lstat(fnbuf, &st);
350                 if(S_ISLNK(st.st_mode))
351                 {
352                         int len;
353                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
354                         if(len!=-1)
355                         {
356                                 linkbuf[len] = 0;
357                                 node = linkbuf;
358                         }
359                 }
360
361                 if(checked)
362                 {
363                         for(i=0; (!duplicate && i<n_checked); ++i)
364                                 if(strcmp(node, checked[i])==0)
365                                         duplicate = 1;
366                 }
367                 if(duplicate)
368                 {
369                         if(verbosity>=2)
370                                 printf("Device %s is a duplicate\n", fnbuf);
371                         continue;
372                 }
373
374                 checked = (char **)realloc(checked, (n_checked+1)*sizeof(char *));
375                 checked[n_checked] = strdup(node);
376                 ++n_checked;
377
378                 nodes = (char **)realloc(nodes, (n_nodes+2)*sizeof(char *));
379                 nodes[n_nodes] = strdup(fnbuf);
380                 ++n_nodes;
381         }
382
383         closedir(dir);
384         if(checked)
385         {
386                 for(i=0; i<n_checked; ++i)
387                         free(checked[i]);
388                 free(checked);
389         }
390
391         if(nodes)
392                 nodes[n_nodes] = NULL;
393
394         return nodes;
395 }
396
397 Device *get_devices(void)
398 {
399         char **nodes = NULL;
400         Device *devices = NULL;
401         int n_devices = 0;
402         char **mounted = NULL;
403         int i;
404
405         nodes = get_device_nodes("/dev/disk/by-id");
406         mounted = get_mounted_devices();
407
408         for(i=0; nodes[i]; ++i)
409         {
410                 if(verbosity>=1)
411                         printf("Examining device %s\n", nodes[i]);
412
413                 Property *props = get_device_properties(nodes[i]);
414                 if(!props)
415                 {
416                         if(verbosity>=2)
417                                 printf("  No properties\n");
418                         continue;
419                 }
420
421                 if(verbosity>=2)
422                 {
423                         int j;
424                         for(j=0; props[j].name; ++j)
425                                 printf("  %s = %s\n", props[j].name, props[j].value);
426                 }
427
428                 if(can_mount(props))
429                 {
430                         char *devname;
431                         char *label;
432                         char *vendor;
433                         char *model;
434                         char buf[256];
435                         char pos;
436                         struct stat st;
437
438                         if(verbosity>=1)
439                                 printf("  Using device\n");
440
441                         devname = get_property_value(props, "DEVNAME");
442
443                         label = get_property_value(props, "ID_FS_LABEL");
444                         if(!label)
445                                 label = get_property_value(props, "ID_FS_UUID");
446                         if(!label)
447                         {
448                                 char *ptr;
449
450                                 label = devname;
451                                 for(ptr=label; *ptr; ++ptr)
452                                         if(*ptr=='/')
453                                                 label = ptr+1;
454                         }
455
456                         vendor = get_property_value(props, "ID_VENDOR");
457                         model = get_property_value(props, "ID_MODEL");
458
459                         pos = snprintf(buf, sizeof(buf), "%s", label);
460                         if(vendor && model)
461                                 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
462
463                         stat(nodes[i], &st);
464
465                         devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
466                         devices[n_devices].node = nodes[i];
467                         devices[n_devices].label = strdup(label);
468                         devices[n_devices].description = strdup(buf);
469                         devices[n_devices].mounted = is_in_array(mounted, devname);
470                         devices[n_devices].time = st.st_mtime;
471                         ++n_devices;
472                 }
473                 else
474                         free(nodes[i]);
475                 free_properties(props);
476         }
477
478         free(nodes);
479         free_device_names(mounted);
480
481         if(devices)
482         {
483                 devices[n_devices].node = NULL;
484                 devices[n_devices].label = NULL;
485                 devices[n_devices].description = NULL;
486         }
487
488         return devices;
489 }
490
491 void free_devices(Device *devices)
492 {
493         int i;
494         if(!devices)
495                 return;
496         for(i=0; devices[i].node; ++i)
497         {
498                 free(devices[i].node);
499                 free(devices[i].label);
500                 free(devices[i].description);
501         }
502         free(devices);
503 }
504
505 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
506 {
507         GtkTreeModel *model;
508         GtkTreeIter iter;
509         int umount = *(int *)user_data;
510
511         model = gtk_tree_view_get_model(list);
512
513         if(gtk_tree_model_get_iter(model, &iter, path))
514         {
515                 Device *device;
516                 int pid;
517                 int pipe_fd[2];
518
519                 gtk_tree_model_get(model, &iter, 1, &device, -1);
520
521                 pipe(pipe_fd);
522
523                 pid = fork();
524                 if(pid==0)
525                 {
526                         if(verbosity>=1)
527                         {
528                                 if(umount)
529                                         printf("Running pumount %s\n", device->node);
530                                 else
531                                         printf("Running pmount %s %s\n", device->node, device->label);
532                         }
533
534                         close(pipe_fd[0]);
535                         dup2(pipe_fd[1], 1);
536                         dup2(pipe_fd[1], 2);
537
538                         if(umount)
539                                 execl("/usr/bin/pumount", "pumount", device->node, NULL);
540                         else
541                                 execl("/usr/bin/pmount", "pmount", device->node, device->label, NULL);
542                         _exit(1);
543                 }
544                 else if(pid>0)
545                 {
546                         char buf[1024];
547                         int pos = 0;
548                         int status;
549
550                         close(pipe_fd[1]);
551
552                         while(1)
553                         {
554                                 int len;
555
556                                 len = read(pipe_fd[0], buf+pos, sizeof(buf)-pos-1);
557                                 if(len<=0)
558                                         break;
559                                 pos += len;
560                         }
561
562                         buf[pos] = 0;
563
564                         waitpid(pid, &status, 0);
565                         if(!WIFEXITED(status) || WEXITSTATUS(status))
566                         {
567                                 GtkWidget *dialog;
568
569                                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf);
570                                 g_signal_connect(dialog, "response", &gtk_main_quit, NULL);
571                                 gtk_widget_show_all(dialog);
572                         }
573                         else
574                                 gtk_main_quit();
575                 }
576                 else
577                 {
578                 }
579         }
580
581         (void)column;
582 }
583
584 gboolean key_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
585 {
586         if(event->key.keyval==GDK_KEY_Escape)
587         {
588                 gtk_main_quit();
589                 return TRUE;
590         }
591
592         (void)widget;
593         (void)user_data;
594
595         return FALSE;
596 }
597
598 int main(int argc, char **argv)
599 {
600         GtkWidget *window;
601         GtkWidget *viewport;
602         GtkWidget *list;
603         GtkListStore *store;
604         GtkTreeSelection *selection;
605         GtkTreeIter iter;
606         Device *devices;
607         int i;
608         time_t latest;
609         int opt;
610         int umount = 0;
611         int n_listed;
612
613         gtk_init(&argc, &argv);
614
615         while((opt = getopt(argc, argv, "vu"))!=-1) switch(opt)
616         {
617         case 'v':
618                 ++verbosity;
619                 break;
620         case 'u':
621                 umount = 1;
622                 break;
623         }
624
625         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
626         gtk_container_set_border_width(GTK_CONTAINER(window), 5);
627         g_signal_connect(window, "destroy", G_CALLBACK(&gtk_main_quit), NULL);
628         g_signal_connect(window, "key-press-event", G_CALLBACK(&key_press), NULL);
629
630         viewport = gtk_viewport_new(NULL, NULL);
631         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
632         gtk_container_add(GTK_CONTAINER(window), viewport);
633
634         list = gtk_tree_view_new();
635         gtk_container_add(GTK_CONTAINER(viewport), list);
636         g_signal_connect(list, "row-activated", G_CALLBACK(&row_activated), &umount);
637
638         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
639         gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
640         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list),
641                 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
642
643         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
644
645         devices = get_devices();
646         n_listed = 0;
647         if(devices)
648         {
649                 latest = 0;
650                 for(i=0; devices[i].node; ++i)
651                         if(!devices[i].mounted==!umount)
652                         {
653                                 gtk_list_store_append(store, &iter);
654                                 gtk_list_store_set(store, &iter, 0, devices[i].description, 1, &devices[i], -1);
655                                 if(devices[i].time>latest)
656                                 {
657                                         latest = devices[i].time;
658                                         gtk_tree_selection_select_iter(selection, &iter);
659                                 }
660
661                                 ++n_listed;
662                         }
663
664         }
665
666         if(n_listed)
667                 gtk_widget_show_all(window);
668         else
669         {
670                 GtkWidget *dialog;
671
672                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
673                         "No devices to %s", (umount ? "unmount" : "mount"));
674                 g_signal_connect(dialog, "response", G_CALLBACK(&gtk_main_quit), NULL);
675                 gtk_widget_show_all(dialog);
676         }
677
678         gtk_main();
679
680         free_devices(devices);
681
682         return 0;
683 }