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