Add verbose flag and unmount mode
[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 Device *get_devices(void)
212 {
213         DIR *dir;
214         struct dirent *de;
215         char fnbuf[256];
216         Device *devices = NULL;
217         int n_devices = 0;
218         char **mounted = NULL;
219         
220         dir = opendir("/dev/disk/by-id");
221         if(!dir)
222                 return NULL;
223
224         mounted = get_mounted_devices();
225
226         while((de = readdir(dir)))
227         {
228                 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
229                         continue;
230
231                 snprintf(fnbuf, sizeof(fnbuf), "/dev/disk/by-id/%s", de->d_name);
232                 if(verbosity>=1)
233                         printf("Examining device %s\n", fnbuf);
234
235                 Property *props = get_device_properties(fnbuf);
236                 if(!props)
237                 {
238                         if(verbosity>=2)
239                                 printf("  No properties\n");
240                         continue;
241                 }
242
243                 if(verbosity>=2)
244                 {
245                         int i;
246                         for(i=0; props[i].name; ++i)
247                                 printf("  %s = %s\n", props[i].name, props[i].value);
248                 }
249
250                 if(match_property_value(props, "ID_BUS", "usb") && match_property_value(props, "DEVTYPE", "partition"))
251                 {
252                         char *devname;
253                         char *label;
254                         char *vendor;
255                         char *model;
256                         char buf[256];
257                         char pos;
258                         struct stat st;
259
260                         if(verbosity>=1)
261                                 printf("  Using device\n");
262
263                         devname = get_property_value(props, "DEVNAME");
264
265                         label = get_property_value(props, "ID_FS_LABEL");
266                         if(!label)
267                                 label = get_property_value(props, "ID_FS_UUID");
268                         if(!label)
269                         {
270                                 char *ptr;
271
272                                 label = devname;
273                                 for(ptr=label; *ptr; ++ptr)
274                                         if(*ptr=='/')
275                                                 label = ptr+1;
276                         }
277
278                         vendor = get_property_value(props, "ID_VENDOR");
279                         model = get_property_value(props, "ID_MODEL");
280
281                         pos = snprintf(buf, sizeof(buf), "%s", label);
282                         if(vendor && model)
283                                 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
284
285                         stat(fnbuf, &st);
286
287                         devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
288                         devices[n_devices].node = strdup(fnbuf);
289                         devices[n_devices].label = strdup(label);
290                         devices[n_devices].description = strdup(buf);
291                         devices[n_devices].mounted = is_mounted(mounted, devname);
292                         devices[n_devices].time = st.st_mtime;
293                         ++n_devices;
294                 }
295                 free_properties(props);
296         }
297
298         closedir(dir);
299         free_mounted_devices(mounted);
300
301         if(devices)
302         {
303                 devices[n_devices].node = NULL;
304                 devices[n_devices].label = NULL;
305                 devices[n_devices].description = NULL;
306         }
307
308         return devices;
309 }
310
311 void free_devices(Device *devices)
312 {
313         int i;
314         if(!devices)
315                 return;
316         for(i=0; devices[i].node; ++i)
317         {
318                 free(devices[i].node);
319                 free(devices[i].label);
320         }
321         free(devices);
322 }
323
324 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
325 {
326         GtkTreeModel *model;
327         GtkTreeIter iter;
328         int umount = *(int *)user_data;
329
330         model = gtk_tree_view_get_model(list);
331
332         if(gtk_tree_model_get_iter(model, &iter, path))
333         {
334                 Device *device;
335                 int pid;
336                 int pipe_fd[2];
337
338                 gtk_tree_model_get(model, &iter, 1, &device, -1);
339
340                 pipe(pipe_fd);
341
342                 pid = fork();
343                 if(pid==0)
344                 {
345                         if(verbosity>=2)
346                         {
347                                 if(umount)
348                                         printf("Running pumount %s\n", device->node);
349                                 else
350                                         printf("Running pmount %s %s\n", device->node, device->label);
351                         }
352
353                         close(pipe_fd[0]);
354                         dup2(pipe_fd[1], 1);
355                         dup2(pipe_fd[1], 2);
356
357                         if(umount)
358                                 execl("/usr/bin/pumount", "pumount", device->node, NULL);
359                         else
360                                 execl("/usr/bin/pmount", "pmount", device->node, device->label, NULL);
361                         _exit(1);
362                 }
363                 else if(pid>0)
364                 {
365                         char buf[1024];
366                         int pos = 0;
367                         int status;
368
369                         close(pipe_fd[1]);
370
371                         while(1)
372                         {
373                                 int len;
374
375                                 len = read(pipe_fd[0], buf+pos, sizeof(buf)-pos-1);
376                                 if(len<=0)
377                                         break;
378                                 pos += len;
379                         }
380
381                         buf[pos] = 0;
382
383                         waitpid(pid, &status, 0);
384                         if(!WIFEXITED(status) || WEXITSTATUS(status))
385                         {
386                                 GtkWidget *dialog;
387
388                                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf);
389                                 g_signal_connect(dialog, "response", &gtk_main_quit, NULL);
390                                 gtk_widget_show_all(dialog);
391                         }
392                         else
393                                 gtk_main_quit();
394                 }
395                 else
396                 {
397                 }
398         }
399
400         (void)column;
401 }
402
403 int main(int argc, char **argv)
404 {
405         GtkWidget *window;
406         GtkWidget *viewport;
407         GtkWidget *list;
408         GtkListStore *store;
409         GtkTreeSelection *selection;
410         GtkTreeIter iter;
411         Device *devices;
412         int i;
413         time_t latest;
414         int opt;
415         int umount = 0;
416         int n_listed;
417
418         gtk_init(&argc, &argv);
419
420         while((opt = getopt(argc, argv, "vu"))!=-1) switch(opt)
421         {
422         case 'v':
423                 ++verbosity;
424                 break;
425         case 'u':
426                 umount = 1;
427                 break;
428         }
429
430         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
431         gtk_container_set_border_width(GTK_CONTAINER(window), 5);
432         g_signal_connect(window, "destroy", &gtk_main_quit, NULL);
433
434         viewport = gtk_viewport_new(NULL, NULL);
435         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
436         gtk_container_add(GTK_CONTAINER(window), viewport);
437
438         list = gtk_tree_view_new();
439         gtk_container_add(GTK_CONTAINER(viewport), list);
440         g_signal_connect(list, "row-activated", (GCallback)&row_activated, &umount);
441
442         store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
443         gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
444         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(list),
445                 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
446
447         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));
448
449         devices = get_devices();
450         n_listed = 0;
451         if(devices)
452         {
453                 latest = 0;
454                 for(i=0; devices[i].node; ++i)
455                         if(!devices[i].mounted==!umount)
456                         {
457                                 gtk_list_store_append(store, &iter);
458                                 gtk_list_store_set(store, &iter, 0, devices[i].description, 1, &devices[i], -1);
459                                 if(devices[i].time>latest)
460                                 {
461                                         latest = devices[i].time;
462                                         gtk_tree_selection_select_iter(selection, &iter);
463                                 }
464
465                                 ++n_listed;
466                         }
467
468         }
469
470         if(n_listed)
471                 gtk_widget_show_all(window);
472         else
473         {
474                 GtkWidget *dialog;
475
476                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
477                         "No devices to %s", (umount ? "unmount" : "mount"));
478                 g_signal_connect(dialog, "response", &gtk_main_quit, NULL);
479                 gtk_widget_show_all(dialog);
480         }
481
482         gtk_main();
483
484         free_devices(devices);
485
486         return 0;
487 }