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