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