Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| /* | |
| * | |
| * oFono - Open Source Telephony | |
| * | |
| * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| * | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include <glib.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <fcntl.h> | |
| #include <errno.h> | |
| #include <unistd.h> | |
| #include <dirent.h> | |
| #include "ofono.h" | |
| #include "simfs.h" | |
| #include "simutil.h" | |
| #include "storage.h" | |
| #define SIM_CACHE_MODE 0600 | |
| #define SIM_CACHE_BASEPATH STORAGEDIR "/%s-%i" | |
| #define SIM_CACHE_VERSION SIM_CACHE_BASEPATH "/version" | |
| #define SIM_CACHE_PATH SIM_CACHE_BASEPATH "/%04x" | |
| #define SIM_CACHE_HEADER_SIZE 39 | |
| #define SIM_FILE_INFO_SIZE 7 | |
| #define SIM_IMAGE_CACHE_BASEPATH STORAGEDIR "/%s-%i/images" | |
| #define SIM_IMAGE_CACHE_PATH SIM_IMAGE_CACHE_BASEPATH "/%d.xpm" | |
| #define SIM_FS_VERSION 2 | |
| static gboolean sim_fs_op_next(gpointer user_data); | |
| static gboolean sim_fs_op_read_record(gpointer user); | |
| static gboolean sim_fs_op_read_block(gpointer user_data); | |
| struct sim_fs_op { | |
| int id; | |
| unsigned char *buffer; | |
| enum ofono_sim_file_structure structure; | |
| unsigned short offset; | |
| gboolean info_only; | |
| int num_bytes; | |
| int length; | |
| int record_length; | |
| int current; | |
| unsigned char path[6]; | |
| unsigned char path_len; | |
| gconstpointer cb; | |
| gboolean is_read; | |
| void *userdata; | |
| struct ofono_sim_context *context; | |
| }; | |
| struct ofono_sim_context { | |
| struct sim_fs *fs; | |
| struct ofono_watchlist *file_watches; | |
| }; | |
| struct sim_fs { | |
| GQueue *op_q; | |
| gint op_source; | |
| unsigned char bitmap[32]; | |
| int fd; | |
| struct ofono_sim *sim; | |
| const struct ofono_sim_driver *driver; | |
| GSList *contexts; | |
| struct ofono_sim_aid_session *session; | |
| int session_id; | |
| unsigned int watch_id; | |
| }; | |
| static void sim_fs_op_free(gpointer pointer) | |
| { | |
| struct sim_fs_op *node = pointer; | |
| g_free(node->buffer); | |
| g_free(node); | |
| } | |
| void sim_fs_free(struct sim_fs *fs) | |
| { | |
| if (fs == NULL) | |
| return; | |
| if (fs->op_source) { | |
| g_source_remove(fs->op_source); | |
| fs->op_source = 0; | |
| } | |
| /* | |
| * Note: users of sim_fs must not assume that the callback happens | |
| * for operations still in progress | |
| */ | |
| if (fs->op_q) { | |
| g_queue_free_full(fs->op_q, sim_fs_op_free); | |
| fs->op_q = NULL; | |
| } | |
| while (fs->contexts) | |
| sim_fs_context_free(fs->contexts->data); | |
| if (fs->watch_id) | |
| __ofono_sim_remove_session_watch(fs->session, fs->watch_id); | |
| g_free(fs); | |
| } | |
| struct file_watch { | |
| struct ofono_watchlist_item item; | |
| int ef; | |
| }; | |
| struct sim_fs *sim_fs_new(struct ofono_sim *sim, | |
| const struct ofono_sim_driver *driver) | |
| { | |
| struct sim_fs *fs; | |
| fs = g_try_new0(struct sim_fs, 1); | |
| if (fs == NULL) | |
| return NULL; | |
| fs->sim = sim; | |
| fs->driver = driver; | |
| fs->fd = -1; | |
| return fs; | |
| } | |
| struct ofono_sim_context *sim_fs_context_new(struct sim_fs *fs) | |
| { | |
| struct ofono_sim_context *context = | |
| g_try_new0(struct ofono_sim_context, 1); | |
| if (context == NULL) | |
| return NULL; | |
| context->fs = fs; | |
| fs->contexts = g_slist_prepend(fs->contexts, context); | |
| return context; | |
| } | |
| struct ofono_sim_context *sim_fs_context_new_with_aid(struct sim_fs *fs, | |
| unsigned char *aid) | |
| { | |
| struct ofono_sim_context *context = sim_fs_context_new(fs); | |
| if (context == NULL) | |
| return NULL; | |
| context->fs->session = __ofono_sim_get_session_by_aid(fs->sim, aid); | |
| if (!context->fs->session) { | |
| sim_fs_context_free(context); | |
| return NULL; | |
| } | |
| return context; | |
| } | |
| void sim_fs_context_free(struct ofono_sim_context *context) | |
| { | |
| struct sim_fs *fs = context->fs; | |
| int n = 0; | |
| struct sim_fs_op *op; | |
| if (fs->op_q) { | |
| while ((op = g_queue_peek_nth(fs->op_q, n)) != NULL) { | |
| if (op->context != context) { | |
| n += 1; | |
| continue; | |
| } | |
| if (n == 0) { | |
| op->cb = NULL; | |
| op->context = NULL; | |
| n += 1; | |
| continue; | |
| } | |
| sim_fs_op_free(op); | |
| g_queue_remove(fs->op_q, op); | |
| } | |
| } | |
| if (context->file_watches) | |
| __ofono_watchlist_free(context->file_watches); | |
| fs->contexts = g_slist_remove(fs->contexts, context); | |
| g_free(context); | |
| } | |
| unsigned int sim_fs_file_watch_add(struct ofono_sim_context *context, int id, | |
| ofono_sim_file_changed_cb_t cb, | |
| void *userdata, | |
| ofono_destroy_func destroy) | |
| { | |
| struct file_watch *watch; | |
| if (cb == NULL) | |
| return 0; | |
| if (context->file_watches == NULL) | |
| context->file_watches = __ofono_watchlist_new(g_free); | |
| watch = g_new0(struct file_watch, 1); | |
| watch->ef = id; | |
| watch->item.notify = cb; | |
| watch->item.notify_data = userdata; | |
| watch->item.destroy = destroy; | |
| return __ofono_watchlist_add_item(context->file_watches, | |
| (struct ofono_watchlist_item *) watch); | |
| } | |
| void sim_fs_file_watch_remove(struct ofono_sim_context *context, | |
| unsigned int id) | |
| { | |
| __ofono_watchlist_remove_item(context->file_watches, id); | |
| } | |
| void sim_fs_notify_file_watches(struct sim_fs *fs, int id) | |
| { | |
| GSList *l; | |
| for (l = fs->contexts; l; l = l->next) { | |
| struct ofono_sim_context *context = l->data; | |
| GSList *k; | |
| if (context->file_watches == NULL) | |
| continue; | |
| for (k = context->file_watches->items; k; k = k->next) { | |
| struct file_watch *w = k->data; | |
| ofono_sim_file_changed_cb_t notify = w->item.notify; | |
| if (id == -1 || w->ef == id) | |
| notify(w->ef, w->item.notify_data); | |
| } | |
| } | |
| } | |
| static void sim_fs_end_current(struct sim_fs *fs) | |
| { | |
| struct sim_fs_op *op = g_queue_pop_head(fs->op_q); | |
| if (g_queue_get_length(fs->op_q) > 0) | |
| fs->op_source = g_idle_add(sim_fs_op_next, fs); | |
| else if (fs->watch_id) /* release the session if no pending reads */ | |
| __ofono_sim_remove_session_watch(fs->session, fs->watch_id); | |
| if (fs->fd != -1) { | |
| TFR(close(fs->fd)); | |
| fs->fd = -1; | |
| } | |
| memset(fs->bitmap, 0, sizeof(fs->bitmap)); | |
| sim_fs_op_free(op); | |
| } | |
| static void sim_fs_op_error(struct sim_fs *fs) | |
| { | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| if (op->cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return; | |
| } | |
| if (op->info_only == TRUE) | |
| ((sim_fs_read_info_cb_t) op->cb) | |
| (0, 0, 0, 0, op->userdata); | |
| else if (op->is_read == TRUE) | |
| ((ofono_sim_file_read_cb_t) op->cb) | |
| (0, 0, 0, 0, 0, op->userdata); | |
| else | |
| ((ofono_sim_file_write_cb_t) op->cb) | |
| (0, op->userdata); | |
| sim_fs_end_current(fs); | |
| } | |
| static gboolean cache_block(struct sim_fs *fs, int block, int block_len, | |
| const unsigned char *data, int num_bytes) | |
| { | |
| int offset; | |
| int bit; | |
| ssize_t r; | |
| unsigned char b; | |
| if (fs->fd == -1) | |
| return FALSE; | |
| if (lseek(fs->fd, block * block_len + | |
| SIM_CACHE_HEADER_SIZE, SEEK_SET) == (off_t) -1) | |
| return FALSE; | |
| r = TFR(write(fs->fd, data, num_bytes)); | |
| if (r != num_bytes) | |
| return FALSE; | |
| /* update present bit for this block */ | |
| offset = block / 8; | |
| bit = block % 8; | |
| /* lseek to correct byte (skip file info) */ | |
| lseek(fs->fd, offset + SIM_FILE_INFO_SIZE, SEEK_SET); | |
| b = fs->bitmap[offset]; | |
| b |= 1 << bit; | |
| r = TFR(write(fs->fd, &b, sizeof(b))); | |
| if (r != sizeof(b)) | |
| return FALSE; | |
| fs->bitmap[offset] = b; | |
| return TRUE; | |
| } | |
| static void sim_fs_op_write_cb(const struct ofono_error *error, void *data) | |
| { | |
| struct sim_fs *fs = data; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| ofono_sim_file_write_cb_t cb = op->cb; | |
| if (cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return; | |
| } | |
| if (error->type == OFONO_ERROR_TYPE_NO_ERROR) | |
| cb(1, op->userdata); | |
| else | |
| cb(0, op->userdata); | |
| sim_fs_end_current(fs); | |
| } | |
| static void sim_fs_op_read_block_cb(const struct ofono_error *error, | |
| const unsigned char *data, int len, | |
| void *user) | |
| { | |
| struct sim_fs *fs = user; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| int start_block; | |
| int end_block; | |
| int bufoff; | |
| int dataoff; | |
| int tocopy; | |
| if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| start_block = op->offset / 256; | |
| end_block = (op->offset + (op->num_bytes - 1)) / 256; | |
| if (op->current == start_block) { | |
| bufoff = 0; | |
| dataoff = op->offset % 256; | |
| tocopy = MIN(256 - op->offset % 256, | |
| op->num_bytes - op->current * 256); | |
| } else { | |
| bufoff = (op->current - start_block - 1) * 256 + | |
| op->offset % 256; | |
| dataoff = 0; | |
| tocopy = MIN(256, op->num_bytes - op->current * 256); | |
| } | |
| DBG("bufoff: %d, dataoff: %d, tocopy: %d", | |
| bufoff, dataoff, tocopy); | |
| memcpy(op->buffer + bufoff, data + dataoff, tocopy); | |
| cache_block(fs, op->current, 256, data, len); | |
| if (op->cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return; | |
| } | |
| op->current++; | |
| if (op->current > end_block) { | |
| ofono_sim_file_read_cb_t cb = op->cb; | |
| cb(1, op->num_bytes, 0, op->buffer, | |
| op->record_length, op->userdata); | |
| sim_fs_end_current(fs); | |
| } else { | |
| fs->op_source = g_idle_add(sim_fs_op_read_block, fs); | |
| } | |
| } | |
| static gboolean sim_fs_op_read_block(gpointer user_data) | |
| { | |
| struct sim_fs *fs = user_data; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| int start_block; | |
| int end_block; | |
| unsigned short read_bytes; | |
| fs->op_source = 0; | |
| if (op->cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return FALSE; | |
| } | |
| start_block = op->offset / 256; | |
| end_block = (op->offset + (op->num_bytes - 1)) / 256; | |
| if (op->current == start_block) { | |
| op->buffer = g_try_new0(unsigned char, op->num_bytes); | |
| if (op->buffer == NULL) { | |
| sim_fs_op_error(fs); | |
| return FALSE; | |
| } | |
| } | |
| while (fs->fd != -1 && op->current <= end_block) { | |
| int offset = op->current / 8; | |
| int bit = 1 << op->current % 8; | |
| int bufoff; | |
| int seekoff; | |
| int toread; | |
| if ((fs->bitmap[offset] & bit) == 0) | |
| break; | |
| if (op->current == start_block) { | |
| bufoff = 0; | |
| seekoff = SIM_CACHE_HEADER_SIZE + op->current * 256 + | |
| op->offset % 256; | |
| toread = MIN(256 - op->offset % 256, | |
| op->num_bytes - op->current * 256); | |
| } else { | |
| bufoff = (op->current - start_block - 1) * 256 + | |
| op->offset % 256; | |
| seekoff = SIM_CACHE_HEADER_SIZE + op->current * 256; | |
| toread = MIN(256, op->num_bytes - op->current * 256); | |
| } | |
| DBG("bufoff: %d, seekoff: %d, toread: %d", | |
| bufoff, seekoff, toread); | |
| if (lseek(fs->fd, seekoff, SEEK_SET) == (off_t) -1) | |
| break; | |
| if (TFR(read(fs->fd, op->buffer + bufoff, toread)) != toread) | |
| break; | |
| op->current += 1; | |
| } | |
| if (op->current > end_block) { | |
| ofono_sim_file_read_cb_t cb = op->cb; | |
| cb(1, op->num_bytes, 0, op->buffer, | |
| op->record_length, op->userdata); | |
| sim_fs_end_current(fs); | |
| return FALSE; | |
| } | |
| if (fs->driver->read_file_transparent == NULL) { | |
| sim_fs_op_error(fs); | |
| return FALSE; | |
| } | |
| read_bytes = MIN(op->length - op->current * 256, 256); | |
| fs->driver->read_file_transparent(fs->sim, op->id, | |
| op->current * 256, | |
| read_bytes, | |
| op->path_len ? op->path : NULL, | |
| op->path_len, | |
| sim_fs_op_read_block_cb, fs); | |
| return FALSE; | |
| } | |
| static void sim_fs_op_retrieve_cb(const struct ofono_error *error, | |
| const unsigned char *data, int len, | |
| void *user) | |
| { | |
| struct sim_fs *fs = user; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| int total = op->length / op->record_length; | |
| ofono_sim_file_read_cb_t cb = op->cb; | |
| if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| cache_block(fs, op->current - 1, op->record_length, | |
| data, op->record_length); | |
| if (cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return; | |
| } | |
| cb(1, op->length, op->current, data, op->record_length, op->userdata); | |
| if (op->current < total) { | |
| op->current += 1; | |
| fs->op_source = g_idle_add(sim_fs_op_read_record, fs); | |
| } else { | |
| sim_fs_end_current(fs); | |
| } | |
| } | |
| static gboolean sim_fs_op_read_record(gpointer user) | |
| { | |
| struct sim_fs *fs = user; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| const struct ofono_sim_driver *driver = fs->driver; | |
| int total = op->length / op->record_length; | |
| unsigned char buf[256]; | |
| fs->op_source = 0; | |
| if (op->cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return FALSE; | |
| } | |
| while (fs->fd != -1 && op->current <= total) { | |
| int offset = (op->current - 1) / 8; | |
| int bit = 1 << ((op->current - 1) % 8); | |
| ofono_sim_file_read_cb_t cb = op->cb; | |
| if ((fs->bitmap[offset] & bit) == 0) | |
| break; | |
| if (lseek(fs->fd, (op->current - 1) * op->record_length + | |
| SIM_CACHE_HEADER_SIZE, SEEK_SET) == (off_t) -1) | |
| break; | |
| if (TFR(read(fs->fd, buf, op->record_length)) != | |
| op->record_length) | |
| break; | |
| cb(1, op->length, op->current, | |
| buf, op->record_length, op->userdata); | |
| op->current += 1; | |
| } | |
| if (op->current > total) { | |
| sim_fs_end_current(fs); | |
| return FALSE; | |
| } | |
| switch (op->structure) { | |
| case OFONO_SIM_FILE_STRUCTURE_FIXED: | |
| if (driver->read_file_linear == NULL) { | |
| sim_fs_op_error(fs); | |
| return FALSE; | |
| } | |
| driver->read_file_linear(fs->sim, op->id, op->current, | |
| op->record_length, | |
| NULL, 0, | |
| sim_fs_op_retrieve_cb, fs); | |
| break; | |
| case OFONO_SIM_FILE_STRUCTURE_CYCLIC: | |
| if (driver->read_file_cyclic == NULL) { | |
| sim_fs_op_error(fs); | |
| return FALSE; | |
| } | |
| driver->read_file_cyclic(fs->sim, op->id, op->current, | |
| op->record_length, | |
| NULL, 0, | |
| sim_fs_op_retrieve_cb, fs); | |
| break; | |
| default: | |
| ofono_error("Unrecognized file structure, this can't happen"); | |
| } | |
| return FALSE; | |
| } | |
| static void sim_fs_op_cache_fileinfo(struct sim_fs *fs, | |
| const struct ofono_error *error, | |
| int length, | |
| enum ofono_sim_file_structure structure, | |
| int record_length, | |
| const unsigned char access[3], | |
| unsigned char file_status) | |
| { | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| const char *imsi = ofono_sim_get_imsi(fs->sim); | |
| enum ofono_sim_phase phase = ofono_sim_get_phase(fs->sim); | |
| enum sim_file_access update; | |
| enum sim_file_access invalidate; | |
| enum sim_file_access rehabilitate; | |
| unsigned char fileinfo[SIM_CACHE_HEADER_SIZE]; | |
| gboolean cache; | |
| char *path; | |
| /* TS 11.11, Section 9.3 */ | |
| update = file_access_condition_decode(access[0] & 0xf); | |
| rehabilitate = file_access_condition_decode((access[2] >> 4) & 0xf); | |
| invalidate = file_access_condition_decode(access[2] & 0xf); | |
| /* Never cache card holder writable files */ | |
| cache = (update == SIM_FILE_ACCESS_ADM || | |
| update == SIM_FILE_ACCESS_NEVER) && | |
| (invalidate == SIM_FILE_ACCESS_ADM || | |
| invalidate == SIM_FILE_ACCESS_NEVER) && | |
| (rehabilitate == SIM_FILE_ACCESS_ADM || | |
| rehabilitate == SIM_FILE_ACCESS_NEVER); | |
| if (imsi == NULL || phase == OFONO_SIM_PHASE_UNKNOWN || cache == FALSE) | |
| return; | |
| memset(fileinfo, 0, SIM_CACHE_HEADER_SIZE); | |
| fileinfo[0] = error->type; | |
| fileinfo[1] = length >> 8; | |
| fileinfo[2] = length & 0xff; | |
| fileinfo[3] = structure; | |
| fileinfo[4] = record_length >> 8; | |
| fileinfo[5] = record_length & 0xff; | |
| fileinfo[6] = file_status; | |
| path = g_strdup_printf(SIM_CACHE_PATH, imsi, phase, op->id); | |
| fs->fd = TFR(open(path, O_WRONLY | O_CREAT | O_TRUNC, SIM_CACHE_MODE)); | |
| g_free(path); | |
| if (fs->fd == -1) | |
| return; | |
| if (TFR(write(fs->fd, fileinfo, SIM_CACHE_HEADER_SIZE)) == | |
| SIM_CACHE_HEADER_SIZE) | |
| return; | |
| TFR(close(fs->fd)); | |
| fs->fd = -1; | |
| } | |
| static void sim_fs_op_info_cb(const struct ofono_error *error, int length, | |
| enum ofono_sim_file_structure structure, | |
| int record_length, | |
| const unsigned char access[3], | |
| unsigned char file_status, | |
| void *data) | |
| { | |
| struct sim_fs *fs = data; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| sim_fs_op_cache_fileinfo(fs, error, length, structure, record_length, | |
| access, file_status); | |
| if (structure != op->structure) { | |
| ofono_error("Requested file structure differs from SIM: %x", | |
| op->id); | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| if (op->cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return; | |
| } | |
| op->structure = structure; | |
| op->length = length; | |
| if (structure == OFONO_SIM_FILE_STRUCTURE_TRANSPARENT) { | |
| if (op->num_bytes == 0) | |
| op->num_bytes = op->length; | |
| op->record_length = length; | |
| op->current = op->offset / 256; | |
| if (op->info_only == FALSE) | |
| fs->op_source = g_idle_add(sim_fs_op_read_block, fs); | |
| } else { | |
| op->record_length = record_length; | |
| op->current = 1; | |
| if (op->info_only == FALSE) | |
| fs->op_source = g_idle_add(sim_fs_op_read_record, fs); | |
| } | |
| if (op->info_only == TRUE) { | |
| /* | |
| * It's an info-only request, so there is no need to request | |
| * actual contents of the EF. Just return the EF-info. | |
| */ | |
| sim_fs_read_info_cb_t cb = op->cb; | |
| cb(1, file_status, op->length, | |
| op->record_length, op->userdata); | |
| sim_fs_end_current(fs); | |
| } | |
| } | |
| static gboolean sim_fs_op_check_cached(struct sim_fs *fs) | |
| { | |
| const char *imsi = ofono_sim_get_imsi(fs->sim); | |
| enum ofono_sim_phase phase = ofono_sim_get_phase(fs->sim); | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| char *path; | |
| int fd; | |
| ssize_t len; | |
| unsigned char fileinfo[SIM_CACHE_HEADER_SIZE]; | |
| int error_type; | |
| int file_length; | |
| enum ofono_sim_file_structure structure; | |
| int record_length; | |
| unsigned char file_status; | |
| if (imsi == NULL || phase == OFONO_SIM_PHASE_UNKNOWN) | |
| return FALSE; | |
| path = g_strdup_printf(SIM_CACHE_PATH, imsi, phase, op->id); | |
| if (path == NULL) | |
| return FALSE; | |
| fd = TFR(open(path, O_RDWR)); | |
| g_free(path); | |
| if (fd == -1) { | |
| if (errno != ENOENT) | |
| DBG("Error %i opening cache file for " | |
| "fileid %04x, IMSI %s", | |
| errno, op->id, imsi); | |
| return FALSE; | |
| } | |
| len = TFR(read(fd, fileinfo, SIM_CACHE_HEADER_SIZE)); | |
| if (len != SIM_CACHE_HEADER_SIZE) | |
| goto error; | |
| error_type = fileinfo[0]; | |
| file_length = (fileinfo[1] << 8) | fileinfo[2]; | |
| structure = fileinfo[3]; | |
| record_length = (fileinfo[4] << 8) | fileinfo[5]; | |
| file_status = fileinfo[6]; | |
| if (structure == OFONO_SIM_FILE_STRUCTURE_TRANSPARENT) | |
| record_length = file_length; | |
| if (record_length == 0 || file_length < record_length) | |
| goto error; | |
| op->length = file_length; | |
| op->record_length = record_length; | |
| memcpy(fs->bitmap, fileinfo + SIM_FILE_INFO_SIZE, | |
| SIM_CACHE_HEADER_SIZE - SIM_FILE_INFO_SIZE); | |
| fs->fd = fd; | |
| if (error_type != OFONO_ERROR_TYPE_NO_ERROR || | |
| structure != op->structure) { | |
| sim_fs_op_error(fs); | |
| return TRUE; | |
| } | |
| if (op->info_only == TRUE) { | |
| /* | |
| * It's an info-only request, so there is no need to request | |
| * actual contents of the EF. Just return the EF-info. | |
| */ | |
| sim_fs_read_info_cb_t cb = op->cb; | |
| cb(1, file_status, op->length, | |
| op->record_length, op->userdata); | |
| sim_fs_end_current(fs); | |
| } else if (structure == OFONO_SIM_FILE_STRUCTURE_TRANSPARENT) { | |
| if (op->num_bytes == 0) | |
| op->num_bytes = op->length; | |
| op->current = op->offset / 256; | |
| fs->op_source = g_idle_add(sim_fs_op_read_block, fs); | |
| } else { | |
| op->current = 1; | |
| fs->op_source = g_idle_add(sim_fs_op_read_record, fs); | |
| } | |
| return TRUE; | |
| error: | |
| TFR(close(fd)); | |
| return FALSE; | |
| } | |
| static void sim_fs_read_session_cb(const struct ofono_error *error, | |
| const unsigned char *sdata, int length, void *data) | |
| { | |
| struct sim_fs *fs = data; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| ofono_sim_file_read_cb_t cb; | |
| if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| cb = op->cb; | |
| cb(TRUE, length, 0, sdata, length, op->userdata); | |
| sim_fs_end_current(fs); | |
| } | |
| static void session_read_info_cb(const struct ofono_error *error, | |
| int filelength, | |
| enum ofono_sim_file_structure structure, | |
| int recordlength, | |
| const unsigned char access[3], | |
| unsigned char file_status, | |
| void *data) | |
| { | |
| struct sim_fs *fs = data; | |
| struct sim_fs_op *op = g_queue_peek_head(fs->op_q); | |
| if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| sim_fs_op_cache_fileinfo(fs, error, filelength, structure, recordlength, | |
| access, file_status); | |
| if (op->info_only) { | |
| sim_fs_read_info_cb_t cb = op->cb; | |
| cb(1, file_status, filelength, recordlength, op->userdata); | |
| sim_fs_end_current(fs); | |
| return; | |
| } | |
| if (op->structure == OFONO_SIM_FILE_STRUCTURE_TRANSPARENT) { | |
| if (!fs->driver->session_read_binary) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| fs->driver->session_read_binary(fs->sim, fs->session_id, | |
| op->id, op->offset, filelength, op->path, | |
| op->path_len, sim_fs_read_session_cb, fs); | |
| } else { | |
| if (!fs->driver->session_read_record) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| fs->driver->session_read_record(fs->sim, fs->session_id, | |
| op->id, op->offset, recordlength, op->path, | |
| op->path_len, sim_fs_read_session_cb, fs); | |
| } | |
| } | |
| static void session_destroy_cb(void *userdata) | |
| { | |
| struct sim_fs *fs = userdata; | |
| fs->watch_id = 0; | |
| } | |
| static void get_session_cb(ofono_bool_t active, int session_id, | |
| void *data) | |
| { | |
| struct sim_fs *fs = data; | |
| struct sim_fs_op *op; | |
| if (!active) { | |
| sim_fs_op_error(fs); | |
| return; | |
| } | |
| op = g_queue_peek_head(fs->op_q); | |
| fs->session_id = session_id; | |
| fs->driver->session_read_info(fs->sim, session_id, op->id, op->path, | |
| op->path_len, session_read_info_cb, fs); | |
| } | |
| static gboolean sim_fs_op_next(gpointer user_data) | |
| { | |
| struct sim_fs *fs = user_data; | |
| const struct ofono_sim_driver *driver = fs->driver; | |
| struct sim_fs_op *op; | |
| fs->op_source = 0; | |
| if (fs->op_q == NULL) | |
| return FALSE; | |
| op = g_queue_peek_head(fs->op_q); | |
| if (op->cb == NULL) { | |
| sim_fs_end_current(fs); | |
| return FALSE; | |
| } | |
| if (op->is_read == TRUE) { | |
| if (sim_fs_op_check_cached(fs)) | |
| return FALSE; | |
| if (!fs->session) { | |
| driver->read_file_info(fs->sim, op->id, | |
| op->path_len ? op->path : NULL, | |
| op->path_len, | |
| sim_fs_op_info_cb, fs); | |
| } else { | |
| if (fs->watch_id) | |
| fs->driver->session_read_info(fs->sim, | |
| fs->session_id, op->id, | |
| op->path, op->path_len, | |
| session_read_info_cb, fs); | |
| else | |
| fs->watch_id = __ofono_sim_add_session_watch( | |
| fs->session, get_session_cb, | |
| fs, session_destroy_cb); | |
| } | |
| } else { | |
| switch (op->structure) { | |
| case OFONO_SIM_FILE_STRUCTURE_TRANSPARENT: | |
| driver->write_file_transparent(fs->sim, op->id, 0, | |
| op->length, op->buffer, | |
| NULL, 0, sim_fs_op_write_cb, fs); | |
| break; | |
| case OFONO_SIM_FILE_STRUCTURE_FIXED: | |
| driver->write_file_linear(fs->sim, op->id, op->current, | |
| op->length, op->buffer, | |
| NULL, 0, sim_fs_op_write_cb, fs); | |
| break; | |
| case OFONO_SIM_FILE_STRUCTURE_CYCLIC: | |
| driver->write_file_cyclic(fs->sim, op->id, | |
| op->length, op->buffer, | |
| NULL, 0, sim_fs_op_write_cb, fs); | |
| break; | |
| default: | |
| ofono_error("Unrecognized file structure, " | |
| "this can't happen"); | |
| } | |
| g_free(op->buffer); | |
| op->buffer = NULL; | |
| } | |
| return FALSE; | |
| } | |
| int sim_fs_read_info(struct ofono_sim_context *context, int id, | |
| enum ofono_sim_file_structure expected_type, | |
| sim_fs_read_info_cb_t cb, void *data) | |
| { | |
| struct sim_fs *fs = context->fs; | |
| struct sim_fs_op *op; | |
| if (cb == NULL) | |
| return -EINVAL; | |
| if (fs->driver == NULL) | |
| return -EINVAL; | |
| if (fs->driver->read_file_info == NULL) | |
| return -ENOSYS; | |
| if (fs->op_q == NULL) | |
| fs->op_q = g_queue_new(); | |
| op = g_try_new0(struct sim_fs_op, 1); | |
| if (op == NULL) | |
| return -ENOMEM; | |
| op->id = id; | |
| op->structure = expected_type; | |
| op->cb = cb; | |
| op->userdata = data; | |
| op->is_read = TRUE; | |
| op->info_only = TRUE; | |
| op->context = context; | |
| g_queue_push_tail(fs->op_q, op); | |
| if (g_queue_get_length(fs->op_q) == 1) | |
| fs->op_source = g_idle_add(sim_fs_op_next, fs); | |
| return 0; | |
| } | |
| int sim_fs_read(struct ofono_sim_context *context, int id, | |
| enum ofono_sim_file_structure expected_type, | |
| unsigned short offset, unsigned short num_bytes, | |
| const unsigned char *path, unsigned int path_len, | |
| ofono_sim_file_read_cb_t cb, void *data) | |
| { | |
| struct sim_fs *fs = context->fs; | |
| struct sim_fs_op *op; | |
| if (cb == NULL) | |
| return -EINVAL; | |
| if (fs->driver == NULL) | |
| return -EINVAL; | |
| /* check driver support for session based read */ | |
| if (fs->session) { | |
| if (!fs->driver->session_read_info) { | |
| cb(0, 0, 0, NULL, 0, data); | |
| return -ENOSYS; | |
| } | |
| } else { | |
| if (!fs->driver->read_file_info) { | |
| cb(0, 0, 0, NULL, 0, data); | |
| return -ENOSYS; | |
| } | |
| } | |
| if (fs->op_q == NULL) | |
| fs->op_q = g_queue_new(); | |
| op = g_try_new0(struct sim_fs_op, 1); | |
| if (op == NULL) | |
| return -ENOMEM; | |
| op->id = id; | |
| op->structure = expected_type; | |
| op->cb = cb; | |
| op->userdata = data; | |
| op->is_read = TRUE; | |
| op->offset = offset; | |
| op->num_bytes = num_bytes; | |
| op->info_only = FALSE; | |
| op->context = context; | |
| memcpy(op->path, path, path_len); | |
| op->path_len = path_len; | |
| g_queue_push_tail(fs->op_q, op); | |
| if (g_queue_get_length(fs->op_q) == 1) | |
| fs->op_source = g_idle_add(sim_fs_op_next, fs); | |
| return 0; | |
| } | |
| int sim_fs_write(struct ofono_sim_context *context, int id, | |
| ofono_sim_file_write_cb_t cb, | |
| enum ofono_sim_file_structure structure, int record, | |
| const unsigned char *data, int length, void *userdata) | |
| { | |
| struct sim_fs *fs = context->fs; | |
| struct sim_fs_op *op; | |
| gconstpointer fn = NULL; | |
| if (cb == NULL) | |
| return -EINVAL; | |
| if (fs->driver == NULL) | |
| return -EINVAL; | |
| switch (structure) { | |
| case OFONO_SIM_FILE_STRUCTURE_TRANSPARENT: | |
| fn = fs->driver->write_file_transparent; | |
| break; | |
| case OFONO_SIM_FILE_STRUCTURE_FIXED: | |
| fn = fs->driver->write_file_linear; | |
| break; | |
| case OFONO_SIM_FILE_STRUCTURE_CYCLIC: | |
| fn = fs->driver->write_file_cyclic; | |
| break; | |
| default: | |
| ofono_error("Unrecognized file structure, this can't happen"); | |
| } | |
| if (fn == NULL) | |
| return -ENOSYS; | |
| if (fs->op_q == NULL) | |
| fs->op_q = g_queue_new(); | |
| op = g_try_new0(struct sim_fs_op, 1); | |
| if (op == NULL) | |
| return -ENOMEM; | |
| op->id = id; | |
| op->cb = cb; | |
| op->userdata = userdata; | |
| op->is_read = FALSE; | |
| op->buffer = g_memdup(data, length); | |
| op->structure = structure; | |
| op->length = length; | |
| op->current = record; | |
| op->context = context; | |
| g_queue_push_tail(fs->op_q, op); | |
| if (g_queue_get_length(fs->op_q) == 1) | |
| fs->op_source = g_idle_add(sim_fs_op_next, fs); | |
| return 0; | |
| } | |
| void sim_fs_cache_image(struct sim_fs *fs, const char *image, int id) | |
| { | |
| const char *imsi; | |
| enum ofono_sim_phase phase; | |
| if (fs == NULL || image == NULL) | |
| return; | |
| imsi = ofono_sim_get_imsi(fs->sim); | |
| if (imsi == NULL) | |
| return; | |
| phase = ofono_sim_get_phase(fs->sim); | |
| if (phase == OFONO_SIM_PHASE_UNKNOWN) | |
| return; | |
| write_file((const unsigned char *) image, strlen(image), | |
| SIM_CACHE_MODE, SIM_IMAGE_CACHE_PATH, imsi, | |
| phase, id); | |
| } | |
| char *sim_fs_get_cached_image(struct sim_fs *fs, int id) | |
| { | |
| const char *imsi; | |
| enum ofono_sim_phase phase; | |
| unsigned short image_length; | |
| int fd; | |
| char *buffer; | |
| char *path; | |
| int len; | |
| struct stat st_buf; | |
| if (fs == NULL) | |
| return NULL; | |
| imsi = ofono_sim_get_imsi(fs->sim); | |
| if (imsi == NULL) | |
| return NULL; | |
| phase = ofono_sim_get_phase(fs->sim); | |
| if (phase == OFONO_SIM_PHASE_UNKNOWN) | |
| return NULL; | |
| path = g_strdup_printf(SIM_IMAGE_CACHE_PATH, imsi, phase, id); | |
| TFR(stat(path, &st_buf)); | |
| fd = TFR(open(path, O_RDONLY)); | |
| g_free(path); | |
| if (fd < 0) | |
| return NULL; | |
| image_length = st_buf.st_size; | |
| buffer = g_try_new0(char, image_length + 1); | |
| if (buffer == NULL) { | |
| TFR(close(fd)); | |
| return NULL; | |
| } | |
| len = TFR(read(fd, buffer, image_length)); | |
| TFR(close(fd)); | |
| if (len != image_length) { | |
| g_free(buffer); | |
| return NULL; | |
| } | |
| return buffer; | |
| } | |
| static void remove_cachefile(const char *imsi, enum ofono_sim_phase phase, | |
| const struct dirent *file) | |
| { | |
| int id; | |
| char *path; | |
| if (file->d_type != DT_REG) | |
| return; | |
| if (sscanf(file->d_name, "%4x", &id) != 1) | |
| return; | |
| path = g_strdup_printf(SIM_CACHE_PATH, imsi, phase, id); | |
| remove(path); | |
| g_free(path); | |
| } | |
| static void remove_imagefile(const char *imsi, enum ofono_sim_phase phase, | |
| const struct dirent *file) | |
| { | |
| int id; | |
| char *path; | |
| if (file->d_type != DT_REG) | |
| return; | |
| if (sscanf(file->d_name, "%d", &id) != 1) | |
| return; | |
| path = g_strdup_printf(SIM_IMAGE_CACHE_PATH, imsi, phase, id); | |
| remove(path); | |
| g_free(path); | |
| } | |
| void sim_fs_check_version(struct sim_fs *fs) | |
| { | |
| const char *imsi = ofono_sim_get_imsi(fs->sim); | |
| enum ofono_sim_phase phase = ofono_sim_get_phase(fs->sim); | |
| unsigned char version; | |
| if (imsi == NULL || phase == OFONO_SIM_PHASE_UNKNOWN) | |
| return; | |
| if (read_file(&version, 1, SIM_CACHE_VERSION, imsi, phase) == 1) | |
| if (version == SIM_FS_VERSION) | |
| return; | |
| sim_fs_cache_flush(fs); | |
| version = SIM_FS_VERSION; | |
| write_file(&version, 1, SIM_CACHE_MODE, SIM_CACHE_VERSION, imsi, phase); | |
| } | |
| void sim_fs_cache_flush(struct sim_fs *fs) | |
| { | |
| const char *imsi = ofono_sim_get_imsi(fs->sim); | |
| enum ofono_sim_phase phase = ofono_sim_get_phase(fs->sim); | |
| char *path = g_strdup_printf(SIM_CACHE_BASEPATH, imsi, phase); | |
| struct dirent **entries; | |
| int len = scandir(path, &entries, NULL, alphasort); | |
| g_free(path); | |
| if (len > 0) { | |
| /* Remove all file ids */ | |
| while (len--) { | |
| remove_cachefile(imsi, phase, entries[len]); | |
| g_free(entries[len]); | |
| } | |
| g_free(entries); | |
| } | |
| sim_fs_image_cache_flush(fs); | |
| } | |
| void sim_fs_cache_flush_file(struct sim_fs *fs, int id) | |
| { | |
| const char *imsi = ofono_sim_get_imsi(fs->sim); | |
| enum ofono_sim_phase phase = ofono_sim_get_phase(fs->sim); | |
| char *path = g_strdup_printf(SIM_CACHE_PATH, imsi, phase, id); | |
| remove(path); | |
| g_free(path); | |
| } | |
| void sim_fs_image_cache_flush(struct sim_fs *fs) | |
| { | |
| const char *imsi = ofono_sim_get_imsi(fs->sim); | |
| enum ofono_sim_phase phase = ofono_sim_get_phase(fs->sim); | |
| char *path = g_strdup_printf(SIM_IMAGE_CACHE_BASEPATH, imsi, phase); | |
| struct dirent **entries; | |
| int len = scandir(path, &entries, NULL, alphasort); | |
| g_free(path); | |
| if (len <= 0) | |
| return; | |
| /* Remove everything */ | |
| while (len--) { | |
| remove_imagefile(imsi, phase, entries[len]); | |
| g_free(entries[len]); | |
| } | |
| g_free(entries); | |
| } | |
| void sim_fs_image_cache_flush_file(struct sim_fs *fs, int id) | |
| { | |
| const char *imsi = ofono_sim_get_imsi(fs->sim); | |
| enum ofono_sim_phase phase = ofono_sim_get_phase(fs->sim); | |
| char *path = g_strdup_printf(SIM_IMAGE_CACHE_PATH, imsi, phase, id); | |
| remove(path); | |
| g_free(path); | |
| } |