1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <assert.h>
18#include <ctype.h>
19#include <dirent.h>
20#include <errno.h>
21#include <unistd.h>
22#include <stdio.h>
23#include <string.h>
24#include <sys/stat.h>
25
26#include "fatblock.h"
27#include "fat.h"
28#include "fdpool.h"
29#include "fs.h"
30#include "utils.h"
31
32static inline int valid_char(int c)
33{
34	return (isalnum(c) ||
35		strchr("!#$%'()-@^_`{}~", c) ||
36		((c >= 128) && (c < 256)));
37}
38
39static int convert_name(char *short_name, const char *long_name)
40{
41	int i;
42
43	const char *s;
44	const char *dot;
45	int c;
46
47	dot = NULL;
48
49	for (s = long_name; *s; s++) {
50		if (*s == '.') {
51			if (dot) {
52				goto short_fail;
53			} else {
54				dot = s;
55			}
56		} else if (!valid_char(*s)) {
57			goto short_fail;
58		}
59	}
60
61	if (dot - long_name > 8) {
62		goto short_fail;
63	}
64
65	if (dot && (s - (dot + 1) > 3)) {
66		goto short_fail;
67	}
68
69	memset(short_name, ' ', 11);
70
71	if (!dot) {
72		dot = s;
73	}
74
75	for (i = 0; i < dot - long_name; i++) {
76		short_name[i] = toupper(long_name[i]);
77	}
78
79	for (i = 0; i < s - dot; i++) {
80		short_name[8 + i] = toupper(dot[1 + i]);
81	}
82
83	return 0;
84
85short_fail:
86	return 1;
87}
88
89struct imported {
90	cluster_t first_cluster;
91	uint32_t size;
92	struct fat_dirent *dot_dot_dirent;
93};
94
95static int import_file(struct fs *fs, char *path, struct imported *out)
96{
97	struct stat st;
98	struct file *f = NULL;
99	char *path_copy = NULL;
100	int ret;
101
102	ret = stat(path, &st);
103	if (ret < 0) {
104		WARN("importing %s: stat failed: %s\n", path, strerror(errno));
105		goto fail;
106	}
107
108	f = malloc(sizeof(struct file));
109	if (!f) {
110		WARN("importing %s: couldn't allocate file struct: "
111		     "out of memory\n", path);
112		ret = MALLOC_FAIL;
113		goto fail;
114	}
115
116	path_copy = strdup(path);
117	if (!path_copy) {
118		WARN("importing %s: couldn't strdup path: out of memory\n",
119		     path);
120		ret = MALLOC_FAIL;
121		goto fail;
122	}
123
124	f->path = path_copy;
125	f->size = st.st_size;
126	f->dev = st.st_dev;
127	f->ino = st.st_ino;
128	f->mtime = st.st_mtime;
129	fdpool_init(&f->pfd);
130
131	ret = fs_alloc_extent(fs, &f->extent,
132                              f->size, EXTENT_TYPE_FILE, &out->first_cluster);
133	if (ret) {
134		WARN("importing %s: couldn't allocate data extent\n", path);
135		goto fail;
136	}
137
138	out->size = f->size;
139	out->dot_dot_dirent = NULL;
140
141	return 0;
142
143fail:
144	if (path_copy)
145		free(path_copy);
146	if (f)
147		free(f);
148	return ret;
149}
150
151struct item {
152	char name[11];
153	struct imported imp;
154	struct item *next;
155	int is_dir;
156};
157
158static struct item *free_items_head;
159
160static struct item *alloc_item(void)
161{
162	struct item *item;
163
164	if (free_items_head) {
165		item = free_items_head;
166		free_items_head = item->next;
167	} else {
168		item = malloc(sizeof(struct item));
169		/* May return NULL if item couldn't be allocated. */
170	}
171
172	return item;
173}
174
175static void free_item(struct item *item)
176{
177	item->next = free_items_head;
178	free_items_head = item;
179}
180
181static void free_items(struct item *head)
182{
183	struct item *tail;
184
185	for (tail = head; tail->next; tail = tail->next);
186
187	tail->next = free_items_head;
188	free_items_head = head;
189}
190
191/* TODO: With some work, this can be rewritten so we don't recurse
192 * until all memory is allocated. */
193static int import_dir(struct fs *fs, char *path, int is_root,
194		      struct imported *out)
195{
196	struct dir *d;
197	cluster_t my_first_cluster;
198
199	DIR *dir;
200	struct dirent *de;
201
202	char ch_path[PATH_MAX];
203	struct imported *ch_imp;
204	cluster_t ch_first_cluster;
205	struct fat_dirent *ch_dirent;
206
207	int ret;
208
209	struct item *items;
210	struct item *item;
211	int count;
212
213	int i;
214
215	dir = opendir(path);
216	if (!dir) {
217		WARN("importing %s: opendir failed: %s\n", path,
218		     strerror(errno));
219		return -1;
220	}
221
222	d = malloc(sizeof(struct dir));
223	if (!d) {
224		WARN("importing %s: couldn't allocate dir struct: "
225		     "out of memory\n", path);
226		closedir(dir);
227		return MALLOC_FAIL;
228	}
229
230	d->path = strdup(path);
231	if (!d->path) {
232		WARN("importing %s: couldn't strdup path: out of memory\n",
233		     path);
234		closedir(dir);
235		free(d);
236		return MALLOC_FAIL;
237	}
238
239	items = NULL;
240	item = NULL;
241	count = 0;
242
243	while ((de = readdir(dir))) {
244		if (de->d_name[0] == '.') {
245			goto skip_item;
246		}
247
248		ret = snprintf(ch_path, PATH_MAX, "%s/%s", path, de->d_name);
249		if (ret < 0 || ret >= PATH_MAX) {
250			goto skip_item;
251		}
252
253		item = alloc_item();
254		if (!item) {
255			WARN("importing %s: couldn't allocate item struct: "
256			     "out of memory\n", path);
257			ret = MALLOC_FAIL;
258			goto free_items;
259		}
260
261		if (convert_name(item->name, de->d_name)) {
262			goto skip_item;
263		}
264
265		switch (de->d_type) {
266			case DT_REG:
267				import_file(fs, ch_path, &item->imp);
268				item->is_dir = 0;
269				break;
270			case DT_DIR:
271				import_dir(fs, ch_path, 0, &item->imp);
272				item->is_dir = 1;
273				break;
274			default:
275				goto skip_item;
276		}
277
278		item->next = items;
279		items = item;
280
281		count++;
282
283		item = NULL;
284
285		continue;
286
287skip_item:
288		if (item)
289			free_item(item);
290	}
291
292	closedir(dir);
293
294	d->size = sizeof(struct fat_dirent) * (count + (is_root ? 0 : 2));
295	ret = fs_alloc_extent(fs, &d->extent, d->size, EXTENT_TYPE_DIR, &out->first_cluster);
296	if (ret) {
297		WARN("importing %s: couldn't allocate directory table extent: "
298		     "out of space\n", path);
299		goto free_items;
300	}
301
302	if (is_root)
303		out->first_cluster = 0;
304
305	my_first_cluster = is_root ? 0 : out->first_cluster;
306
307	d->entries = malloc(sizeof(struct fat_dirent) * (count + (is_root ? 0 : 2)));
308	assert(d->entries);
309	for (i = count - 1; i >= 0; i--) {
310		item = items;
311		items = item->next;
312
313		ch_dirent = &d->entries[i + (is_root ? 0 : 2)];
314
315		fat_dirent_set(ch_dirent,
316                               item->name, item->is_dir ? FAT_ATTR_SUBDIR : 0,
317                               item->imp.first_cluster, item->imp.size);
318
319		if (item->imp.dot_dot_dirent) {
320			fat_dirent_set_first_cluster(item->imp.dot_dot_dirent,
321						     my_first_cluster);
322		}
323
324		free_item(item);
325	}
326
327	if (!is_root) {
328		fat_dirent_set(&d->entries[0],
329                               "..         ", FAT_ATTR_SUBDIR,
330                               (cluster_t)-1, 0);
331		out->dot_dot_dirent = &d->entries[0]; /* will set first_cluster */
332
333		fat_dirent_set(&d->entries[1],
334                               ".          ", FAT_ATTR_SUBDIR,
335                               my_first_cluster, 0);
336	} else {
337		out->dot_dot_dirent = NULL;
338	}
339
340	out->size = 0;
341
342	return 0;
343
344free_items:
345	free_items(items);
346	free(d->path);
347	free(d);
348
349	return ret;
350}
351
352int import_tree(struct fs *fs, char *path)
353{
354	struct imported imp;
355	int ret;
356
357	ret = import_dir(fs, path, 0, &imp);
358	if (ret)
359		return ret;
360
361	fs_set_rootdir_start(fs, imp.first_cluster);
362	fs_update_free_clusters(fs);
363
364	return 0;
365}
366