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