| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include "fatblock.h" |
| #include "fat.h" |
| #include "fdpool.h" |
| #include "fs.h" |
| #include "utils.h" |
| |
| static inline int valid_char(int c) |
| { |
| return (isalnum(c) || |
| strchr("!#$%'()-@^_`{}~", c) || |
| ((c >= 128) && (c < 256))); |
| } |
| |
| static int convert_name(char *short_name, const char *long_name) |
| { |
| int i; |
| |
| const char *s; |
| const char *dot; |
| int c; |
| |
| dot = NULL; |
| |
| for (s = long_name; *s; s++) { |
| if (*s == '.') { |
| if (dot) { |
| goto short_fail; |
| } else { |
| dot = s; |
| } |
| } else if (!valid_char(*s)) { |
| goto short_fail; |
| } |
| } |
| |
| if (dot - long_name > 8) { |
| goto short_fail; |
| } |
| |
| if (dot && (s - (dot + 1) > 3)) { |
| goto short_fail; |
| } |
| |
| memset(short_name, ' ', 11); |
| |
| if (!dot) { |
| dot = s; |
| } |
| |
| for (i = 0; i < dot - long_name; i++) { |
| short_name[i] = toupper(long_name[i]); |
| } |
| |
| for (i = 0; i < s - dot; i++) { |
| short_name[8 + i] = toupper(dot[1 + i]); |
| } |
| |
| return 0; |
| |
| short_fail: |
| return 1; |
| } |
| |
| struct imported { |
| cluster_t first_cluster; |
| uint32_t size; |
| struct fat_dirent *dot_dot_dirent; |
| }; |
| |
| static int import_file(struct fs *fs, char *path, struct imported *out) |
| { |
| struct stat st; |
| struct file *f = NULL; |
| char *path_copy = NULL; |
| int ret; |
| |
| ret = stat(path, &st); |
| if (ret < 0) { |
| WARN("importing %s: stat failed: %s\n", path, strerror(errno)); |
| goto fail; |
| } |
| |
| f = malloc(sizeof(struct file)); |
| if (!f) { |
| WARN("importing %s: couldn't allocate file struct: " |
| "out of memory\n", path); |
| ret = MALLOC_FAIL; |
| goto fail; |
| } |
| |
| path_copy = strdup(path); |
| if (!path_copy) { |
| WARN("importing %s: couldn't strdup path: out of memory\n", |
| path); |
| ret = MALLOC_FAIL; |
| goto fail; |
| } |
| |
| f->path = path_copy; |
| f->size = st.st_size; |
| f->dev = st.st_dev; |
| f->ino = st.st_ino; |
| f->mtime = st.st_mtime; |
| fdpool_init(&f->pfd); |
| |
| ret = fs_alloc_extent(fs, &f->extent, |
| f->size, EXTENT_TYPE_FILE, &out->first_cluster); |
| if (ret) { |
| WARN("importing %s: couldn't allocate data extent\n", path); |
| goto fail; |
| } |
| |
| out->size = f->size; |
| out->dot_dot_dirent = NULL; |
| |
| return 0; |
| |
| fail: |
| if (path_copy) |
| free(path_copy); |
| if (f) |
| free(f); |
| return ret; |
| } |
| |
| struct item { |
| char name[11]; |
| struct imported imp; |
| struct item *next; |
| int is_dir; |
| }; |
| |
| static struct item *free_items_head; |
| |
| static struct item *alloc_item(void) |
| { |
| struct item *item; |
| |
| if (free_items_head) { |
| item = free_items_head; |
| free_items_head = item->next; |
| } else { |
| item = malloc(sizeof(struct item)); |
| /* May return NULL if item couldn't be allocated. */ |
| } |
| |
| return item; |
| } |
| |
| static void free_item(struct item *item) |
| { |
| item->next = free_items_head; |
| free_items_head = item; |
| } |
| |
| static void free_items(struct item *head) |
| { |
| struct item *tail; |
| |
| for (tail = head; tail->next; tail = tail->next); |
| |
| tail->next = free_items_head; |
| free_items_head = head; |
| } |
| |
| /* TODO: With some work, this can be rewritten so we don't recurse |
| * until all memory is allocated. */ |
| static int import_dir(struct fs *fs, char *path, int is_root, |
| struct imported *out) |
| { |
| struct dir *d; |
| cluster_t my_first_cluster; |
| |
| DIR *dir; |
| struct dirent *de; |
| |
| char ch_path[PATH_MAX]; |
| struct imported *ch_imp; |
| cluster_t ch_first_cluster; |
| struct fat_dirent *ch_dirent; |
| |
| int ret; |
| |
| struct item *items; |
| struct item *item; |
| int count; |
| |
| int i; |
| |
| dir = opendir(path); |
| if (!dir) { |
| WARN("importing %s: opendir failed: %s\n", path, |
| strerror(errno)); |
| return -1; |
| } |
| |
| d = malloc(sizeof(struct dir)); |
| if (!d) { |
| WARN("importing %s: couldn't allocate dir struct: " |
| "out of memory\n", path); |
| closedir(dir); |
| return MALLOC_FAIL; |
| } |
| |
| d->path = strdup(path); |
| if (!d->path) { |
| WARN("importing %s: couldn't strdup path: out of memory\n", |
| path); |
| closedir(dir); |
| free(d); |
| return MALLOC_FAIL; |
| } |
| |
| items = NULL; |
| item = NULL; |
| count = 0; |
| |
| while ((de = readdir(dir))) { |
| if (de->d_name[0] == '.') { |
| goto skip_item; |
| } |
| |
| ret = snprintf(ch_path, PATH_MAX, "%s/%s", path, de->d_name); |
| if (ret < 0 || ret >= PATH_MAX) { |
| goto skip_item; |
| } |
| |
| item = alloc_item(); |
| if (!item) { |
| WARN("importing %s: couldn't allocate item struct: " |
| "out of memory\n", path); |
| ret = MALLOC_FAIL; |
| goto free_items; |
| } |
| |
| if (convert_name(item->name, de->d_name)) { |
| goto skip_item; |
| } |
| |
| switch (de->d_type) { |
| case DT_REG: |
| import_file(fs, ch_path, &item->imp); |
| item->is_dir = 0; |
| break; |
| case DT_DIR: |
| import_dir(fs, ch_path, 0, &item->imp); |
| item->is_dir = 1; |
| break; |
| default: |
| goto skip_item; |
| } |
| |
| item->next = items; |
| items = item; |
| |
| count++; |
| |
| item = NULL; |
| |
| continue; |
| |
| skip_item: |
| if (item) |
| free_item(item); |
| } |
| |
| closedir(dir); |
| |
| d->size = sizeof(struct fat_dirent) * (count + (is_root ? 0 : 2)); |
| ret = fs_alloc_extent(fs, &d->extent, d->size, EXTENT_TYPE_DIR, &out->first_cluster); |
| if (ret) { |
| WARN("importing %s: couldn't allocate directory table extent: " |
| "out of space\n", path); |
| goto free_items; |
| } |
| |
| if (is_root) |
| out->first_cluster = 0; |
| |
| my_first_cluster = is_root ? 0 : out->first_cluster; |
| |
| d->entries = malloc(sizeof(struct fat_dirent) * (count + (is_root ? 0 : 2))); |
| assert(d->entries); |
| for (i = count - 1; i >= 0; i--) { |
| item = items; |
| items = item->next; |
| |
| ch_dirent = &d->entries[i + (is_root ? 0 : 2)]; |
| |
| fat_dirent_set(ch_dirent, |
| item->name, item->is_dir ? FAT_ATTR_SUBDIR : 0, |
| item->imp.first_cluster, item->imp.size); |
| |
| if (item->imp.dot_dot_dirent) { |
| fat_dirent_set_first_cluster(item->imp.dot_dot_dirent, |
| my_first_cluster); |
| } |
| |
| free_item(item); |
| } |
| |
| if (!is_root) { |
| fat_dirent_set(&d->entries[0], |
| ".. ", FAT_ATTR_SUBDIR, |
| (cluster_t)-1, 0); |
| out->dot_dot_dirent = &d->entries[0]; /* will set first_cluster */ |
| |
| fat_dirent_set(&d->entries[1], |
| ". ", FAT_ATTR_SUBDIR, |
| my_first_cluster, 0); |
| } else { |
| out->dot_dot_dirent = NULL; |
| } |
| |
| out->size = 0; |
| |
| return 0; |
| |
| free_items: |
| free_items(items); |
| free(d->path); |
| free(d); |
| |
| return ret; |
| } |
| |
| int import_tree(struct fs *fs, char *path) |
| { |
| struct imported imp; |
| int ret; |
| |
| ret = import_dir(fs, path, 0, &imp); |
| if (ret) |
| return ret; |
| |
| fs_set_rootdir_start(fs, imp.first_cluster); |
| fs_update_free_clusters(fs); |
| |
| return 0; |
| } |