libfstools: ubi: avoid crash in volume finding function
[project/fstools.git] / libfstools / ubi.c
1 /*
2  * Copyright (C) 2014 Daniel Golle <daniel@makrotopia.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License version 2.1
6  * as published by the Free Software Foundation
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <dirent.h>
18
19 #include "libfstools.h"
20 #include "volume.h"
21
22 /* fit for UBI_MAX_VOLUME_NAME and sysfs path lengths */
23 #define BUFLEN          128
24
25 /* could use libubi-tiny instead, but already had the code directly reading
26  * from sysfs */
27 const char *const ubi_dir_name = "/sys/devices/virtual/ubi";
28
29 struct ubi_priv {
30         int             ubi_num;
31         int             ubi_volid;
32 };
33
34 static struct driver ubi_driver;
35
36 static int
37 read_uint_from_file(char *dirname, char *filename, unsigned int *i)
38 {
39         FILE *f;
40         char fname[BUFLEN];
41         int ret = -1;
42
43         snprintf(fname, sizeof(fname), "%s/%s", dirname, filename);
44
45         f = fopen(fname, "r");
46         if (!f)
47                 return ret;
48
49         if (fscanf(f, "%u", i) == 1)
50                 ret = 0;
51
52         fclose(f);
53         return ret;
54 }
55
56 static char
57 *read_string_from_file(char *dirname, char *filename)
58 {
59         FILE *f;
60         char fname[BUFLEN];
61         char buf[BUFLEN];
62         int i;
63
64         snprintf(fname, sizeof(fname), "%s/%s", dirname, filename);
65
66         f = fopen(fname, "r");
67         if (!f)
68                 return NULL;
69
70         if (fgets(buf, sizeof(buf), f) == NULL)
71                 return NULL;
72
73         fclose(f);
74
75         /* make sure the string is \0 terminated */
76         buf[sizeof(buf) - 1] = '\0';
77
78         /* remove trailing whitespace */
79         i = strlen(buf) - 1;
80         while (i > 0 && buf[i] <= ' ')
81                 buf[i--] = '\0';
82
83         return strdup(buf);
84 }
85
86 static unsigned int
87 test_open(char *filename)
88 {
89         FILE *f;
90
91         f = fopen(filename, "r");
92         if (!f)
93                 return 0;
94
95         fclose(f);
96         return 1;
97 }
98
99 static int ubi_volume_init(struct volume *v)
100 {
101         char voldir[BUFLEN], voldev[BUFLEN], *volname;
102         struct ubi_priv *p;
103         unsigned int volsize;
104
105         p = (struct ubi_priv*)v->priv;
106
107         snprintf(voldir, sizeof(voldir), "%s/ubi%u/ubi%u_%u",
108                 ubi_dir_name, p->ubi_num, p->ubi_num, p->ubi_volid);
109
110         snprintf(voldev, sizeof(voldev), "/dev/ubi%u_%u",
111                 p->ubi_num, p->ubi_volid);
112
113         volname = read_string_from_file(voldir, "name");
114         if (!volname)
115                 return -1;
116
117         if (read_uint_from_file(voldir, "data_bytes", &volsize))
118                 return -1;
119
120         v->name = volname;
121         v->type = UBIVOLUME;
122         v->size = volsize;
123         v->blk = strdup(voldev);
124
125         return 0;
126 }
127
128 static int ubi_volume_match(struct volume *v, char *name, int ubi_num, int volid)
129 {
130         char voldir[BUFLEN], volblkdev[BUFLEN], *volname;
131         struct ubi_priv *p;
132
133         snprintf(voldir, sizeof(voldir), "%s/ubi%u/ubi%u_%u",
134                 ubi_dir_name, ubi_num, ubi_num, volid);
135
136         snprintf(volblkdev, sizeof(volblkdev), "/dev/ubiblock%u_%u",
137                 ubi_num, volid);
138
139         /* skip if ubiblock device exists */
140         if (test_open(volblkdev))
141                 return -1;
142
143         /* todo: skip existing gluebi device for legacy support */
144
145         volname = read_string_from_file(voldir, "name");
146         if (!volname) {
147                 fprintf(stderr, "Couldn't read %s/name\n", voldir);
148                 return -1;
149         }
150
151         if (strncmp(name, volname, strlen(volname) + 1))
152                 return -1;
153
154         p = calloc(1, sizeof(struct ubi_priv));
155         if (!p)
156                 return -1;
157
158         v->priv = p;
159         v->drv = &ubi_driver;
160         p->ubi_num = ubi_num;
161         p->ubi_volid = volid;
162
163         return ubi_volume_init(v);
164 }
165
166 static int ubi_part_match(struct volume *v, char *name, unsigned int ubi_num)
167 {
168         unsigned int i, volumes_count;
169         char devdir[BUFLEN];
170
171         snprintf(devdir, sizeof(devdir), "%s/ubi%u",
172                 ubi_dir_name, ubi_num);
173
174         if (read_uint_from_file(devdir, "volumes_count", &volumes_count))
175                 return -1;
176
177         for (i=0;i<volumes_count;i++) {
178                 if (!ubi_volume_match(v, name, ubi_num, i)) {
179                         return 0;
180                 }
181         }
182
183         return -1;
184 }
185
186 static int ubi_volume_find(struct volume *v, char *name)
187 {
188         DIR *ubi_dir;
189         struct dirent *ubi_dirent;
190         unsigned int ubi_num;
191         int ret = -1;
192
193         if (find_filesystem("ubifs"))
194                 return ret;
195
196         ubi_dir = opendir(ubi_dir_name);
197         /* check for os ubi support */
198         if (!ubi_dir)
199                 return ret;
200
201         /* probe ubi devices and volumes */
202         while ((ubi_dirent = readdir(ubi_dir)) != NULL) {
203                 if (ubi_dirent->d_name[0] == '.')
204                         continue;
205
206                 sscanf(ubi_dirent->d_name, "ubi%u", &ubi_num);
207                 if (!ubi_part_match(v, name, ubi_num)) {
208                         ret = 0;
209                         break;
210                 };
211         }
212         closedir(ubi_dir);
213         return ret;
214 }
215
216 static int ubi_volume_identify(struct volume *v)
217 {
218         /* Todo: use libblkid-tiny on the ubi chardev */
219         return FS_UBIFS;
220 }
221
222 static struct driver ubi_driver = {
223         .name = "ubi",
224         .find = ubi_volume_find,
225         .init = ubi_volume_init,
226         .identify = ubi_volume_identify,
227 };
228
229 DRIVER(ubi_driver);