/* * Copyright (c) 2018, Real-Thread Information Technology Ltd * All rights reserved * * This software is dual-licensed: 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. For the terms of this * license, see . * * You are free to use this software under the terms of the GNU General * Public License, 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. * * Alternatively, you can license this software under a commercial * license, please send mail to business@rt-thread.com for contact. * * Change Logs: * Date Author Notes * 2018-09-25 ZYH the first version */ #include #include "rtdevice.h" #include #include //#include #include #include #include #include #include #include #include #include //select dfs //select libc //select dstr v0.2.0 #define RDBD_FILE_HASH_FNV_SEED 0x811C9DC5 #undef DBG_ENABLE #define DBG_SECTION_NAME "RDBD FILE" #define DBG_LEVEL DBG_LOG #define DBG_COLOR #include #define RDBD_FILE_CTRL_NEW_FILE 0x40 #define RDBD_FILE_CTRL_WRITE_FILE 0x41 #define RDBD_FILE_CTRL_WRITE_DONE 0x42 #define RDBD_FILE_CTRL_LIST_DIR 0x43 #define RDBD_FILE_CTRL_READ_FILE 0x44 #define RDBD_FILE_CTRL_RM_FILE 0x45 #define RDBD_FTLE_CTRL_GET_HASH 0x46 #define RDBD_FTLE_CTRL_DIR_SYNC_START 0x47 #define RDBD_FTLE_CTRL_DIR_SYNC_STOP 0x48 #define RDBD_FTLE_CTRL_CHECK_PATH 0x49 #define RDBD_FTLE_CTRL_PATH_IS_DIR 0x01 #define RDBD_FTLE_CTRL_PATH_IS_FILE 0x02 #define RDBD_FTLE_CTRL_PATH_NONE 0x00 #define RDBD_FILE_READ_BLOCK_SIZE 4096 #define RDBD_FILE_HEADER_SIZE (sizeof(struct rdbd_file_control_msg_header)) #define RDBD_FILE_MSG_SIZE(x) (RDBD_FILE_MSG_MSG(x)->header.pathlength + RDBD_FILE_MSG_MSG(x)->header.length + RDBD_FILE_HEADER_SIZE) #define RDBD_FILE_MSG_RAW(x) ((rt_uint8_t *)x) #define RDBD_FILE_MSG_MSG(x) ((rdbd_file_ctrl_msg_t)x) struct rdbd_file_control_msg_header { rt_uint16_t command; rt_uint16_t pathlength; rt_uint32_t offset; rt_uint32_t length; }; struct rdbd_file_session { rt_list_t list; char * path; int flag; int fd; }; static struct rdbd_file_session * create_session(rt_list_t * session_list, char * path, int flag) { struct rdbd_file_session * session; if(session_list ) session = rt_calloc(sizeof(struct rdbd_file_session), 1); if(RT_NULL == session_list || RT_NULL == path) { LOG_E("Invalid args %s,%d", __FUNCTION__, __LINE__); return RT_NULL; } session->fd = -1; session->flag = flag; session->path = rt_strdup(path); if(RT_NULL == session->path) { LOG_E("No memory create session"); goto _error; } session->fd = open(session->path, session->flag); if(session->fd < 0) { LOG_E("Open file %s error", session->path); goto _error; } rt_list_insert_before(session_list, &session->list); return session; _error: if(session) { if(session->path) { rt_free(session->path); } if(session->fd >= 0) { close(session->fd); } free(session); } return RT_NULL; } static struct rdbd_file_session * find_session(rt_list_t * session_list, char * path) { rt_list_t * node; if(RT_NULL == session_list || RT_NULL == path) { LOG_E("Invalid args %s,%d", __FUNCTION__, __LINE__); return RT_NULL; } for(node = session_list->next; node != session_list; node = node->next) { if(strcmp(path, rt_list_entry(node, struct rdbd_file_session, list)->path) == 0) { return rt_list_entry(node, struct rdbd_file_session, list); } } return RT_NULL; } static int delete_session(struct rdbd_file_session * session) { if(RT_NULL == session) { LOG_E("Invalid args %s,%d", __FUNCTION__, __LINE__); return -1; } fsync(session->fd); close(session->fd); rt_free(session->path); rt_list_remove(&session->list); rt_free(session); return 0; } static int read_session(struct rdbd_file_session * session, off_t offset, rt_uint8_t * buffer, rt_size_t len) { if(RT_NULL == session || RT_NULL == buffer) { LOG_E("Invalid args %s,%d", __FUNCTION__, __LINE__); return -1; } if((session->flag & 0xF) == O_WRONLY) { LOG_E("Invalid read %s,%d", __FUNCTION__, __LINE__); return -1; } LOG_I("seek to %d", offset); lseek(session->fd, offset, SEEK_SET); LOG_I("Read fd %d, 0x%08X %d", session->fd, buffer, len); return read(session->fd, buffer, len); } static int write_session(struct rdbd_file_session * session, off_t offset, const rt_uint8_t * buffer, rt_size_t len) { if(RT_NULL == session || RT_NULL == buffer) { LOG_E("Invalid args %s,%d", __FUNCTION__, __LINE__); return -1; } if((session->flag & 0xF) == O_RDONLY) { LOG_E("Invalid write %s,%d", __FUNCTION__, __LINE__); return -1; } lseek(session->fd, offset, SEEK_SET); return write(session->fd, buffer, len); } struct rdbd_file_control_msg { struct rdbd_file_control_msg_header header; rt_uint8_t msg[RT_UINT16_MAX]; }; typedef struct rdbd_file_control_msg * rdbd_file_ctrl_msg_t; struct file_request_send { rt_list_t list; char * msg; int msg_pos; int request_file_send; }; static rt_uint32_t rdbd_file_calc_hash(const char * filename); static rt_dstr_t * get_file_list(const char *pathname); static int file_service_request_send(rt_list_t * header, struct rdbd_file_control_msg * msg, int request_file_send); static int file_service_delete_request(struct file_request_send * request); static void file_service_thread_entry(void * arg) { struct rdbd_service * file_service= (struct rdbd_service *)arg; fd_set readset, writeset; int result = 0; int res = 0; int max_fd = file_service->in_pipe_read_fd + 1; rt_list_t request_write_list; rt_list_t session_list; struct file_request_send * request; rdbd_file_ctrl_msg_t msg = RT_NULL; int msg_pos = 0; rt_dstr_t * file_list; char * path; struct rdbd_file_session * session = RT_NULL; rt_list_init(&request_write_list); rt_list_init(&session_list); while(1) { FD_ZERO(&readset); FD_ZERO(&writeset); FD_SET(file_service->in_pipe_read_fd, &readset); if(!rt_list_isempty(&request_write_list)) //need write data to pc { FD_SET(file_service->out_pipe_write_fd, &writeset); if(file_service->out_pipe_write_fd > (max_fd - 1)) { max_fd = file_service->out_pipe_write_fd + 1; } } res = select(max_fd, &readset, &writeset, RT_NULL, RT_NULL); if(res > 0) { if(FD_ISSET(file_service->in_pipe_read_fd, &readset)) { if(msg == RT_NULL) { msg = rt_calloc(RDBD_FILE_HEADER_SIZE, 1); msg_pos = read(file_service->in_pipe_read_fd, RDBD_FILE_MSG_RAW(msg), RDBD_FILE_HEADER_SIZE); if(msg_pos < 0) { msg_pos = 0; } } else if(msg_pos < RDBD_FILE_HEADER_SIZE)//continue read header { result = read(file_service->in_pipe_read_fd, RDBD_FILE_MSG_RAW(msg) + msg_pos, RDBD_FILE_HEADER_SIZE - msg_pos);//read header if(result > 0) { msg_pos += result; } } else if(msg_pos < (RDBD_FILE_MSG_SIZE(msg)))//read body { if(msg_pos == RDBD_FILE_HEADER_SIZE) { msg = rt_realloc(msg, RDBD_FILE_MSG_SIZE(msg));//realloc } result = read(file_service->in_pipe_read_fd, RDBD_FILE_MSG_RAW(msg) + msg_pos, RDBD_FILE_MSG_SIZE(msg) - msg_pos);//read body if(result > 0) { msg_pos += result; } if(msg_pos == RDBD_FILE_MSG_SIZE(msg))//read done { path = rt_calloc(1,msg->header.pathlength + 1); if(RT_NULL == path) { LOG_E("no memory"); RT_ASSERT(0); } rt_strncpy(path, (const char *)msg->msg, msg->header.pathlength); LOG_D("path: %s", path); path[msg->header.pathlength] = '\0'; if(RDBD_FILE_CTRL_NEW_FILE == msg->header.command || RDBD_FILE_CTRL_WRITE_FILE == msg->header.command)//Done { while (1) //replace '\\' to '/' { char *p; p = strchr(path, '\\'); if (p) { *p = '/'; } else { break; } } { char *begin, *end; begin = path; char *_path; while (1) { end = strchr(begin, '/'); //find '\\' since begin if (end == NULL) //if not find '\\' finsh { break; } if (begin == end) //if begin is '\\' { begin += 1; continue; } _path = rt_malloc(end - path + 1); if (_path == RT_NULL) //malloc fail { break;//??? } strncpy(_path, path, end - path); //copy path _path[end - path] = '\0'; { if (access(_path, 0) != 0) //dir not exist { if (mkdir(_path, 0777) != 0) //create fail { LOG_E("mkdir %s failed", _path); free(_path); _path = RT_NULL; break; } } } free(_path); _path = RT_NULL; begin = end + 1; //next } } switch(msg->header.command) { case RDBD_FILE_CTRL_NEW_FILE: session = create_session(&session_list, path, O_CREAT|O_TRUNC|O_WRONLY); if(session == RT_NULL) { LOG_E("Create file error %s", path); RT_ASSERT(0); } break; case RDBD_FILE_CTRL_WRITE_FILE: session = find_session(&session_list, path); if(session == RT_NULL) { LOG_E("Please open the file %s first", path); RT_ASSERT(0); } break; default: RT_ASSERT(0); } LOG_D("Offset %d", msg->header.offset); LOG_D("Write %d", msg->header.length); if(msg->header.length) { if(write_session(session, msg->header.offset, &msg->msg[msg->header.pathlength], msg->header.length) != msg->header.length) { LOG_W("Space not enought"); } } session = RT_NULL; //end of function release mem } else if(RDBD_FILE_CTRL_WRITE_DONE == msg->header.command) { session = find_session(&session_list, path); if(session == RT_NULL) { LOG_W("Not need release session %s", path); RT_ASSERT(0); } delete_session(session); } else if(RDBD_FILE_CTRL_READ_FILE == msg->header.command) { msg = rt_realloc(msg, msg->header.pathlength + RDBD_FILE_READ_BLOCK_SIZE + RDBD_FILE_HEADER_SIZE); if(msg == RT_NULL) { LOG_E("No memory"); RT_ASSERT(0); } session = create_session(&session_list, path, O_RDONLY); if(session == RT_NULL) { LOG_E("Open file error %s", path); RT_ASSERT(0); } msg->header.offset = 0; msg->header.length = read_session(session, msg->header.offset, &msg->msg[msg->header.pathlength], RDBD_FILE_READ_BLOCK_SIZE); file_service_request_send(&request_write_list, msg, 1); msg = RT_NULL; } else if(RDBD_FILE_CTRL_RM_FILE == msg->header.command) { remove(path); } else if(RDBD_FILE_CTRL_LIST_DIR == msg->header.command) { file_list = get_file_list(path); msg->header.offset = 0; msg->header.pathlength = 0; msg->header.length = rt_dstr_strlen(file_list); msg = rt_realloc(msg, RDBD_FILE_MSG_SIZE(msg)); memcpy(msg->msg, file_list->str, msg->header.length); file_service_request_send(&request_write_list, msg, 0); rt_dstr_del(file_list); file_list = RT_NULL; msg = RT_NULL; } else if(RDBD_FTLE_CTRL_GET_HASH == msg->header.command) { msg->header.length = 4; msg->header.offset = 0; msg = rt_realloc(msg, RDBD_FILE_MSG_SIZE(msg)); { rt_uint32_t hash = rdbd_file_calc_hash(path); memcpy(&msg->msg[msg->header.pathlength], &hash, 4); } file_service_request_send(&request_write_list, msg, 0); msg = RT_NULL; } else if(RDBD_FTLE_CTRL_DIR_SYNC_START == msg->header.command) { //TO DO } else if(RDBD_FTLE_CTRL_DIR_SYNC_STOP == msg->header.command) { //TO DO } else if(RDBD_FTLE_CTRL_CHECK_PATH == msg->header.command) { msg->header.length = 1; msg->header.offset = 0; msg = rt_realloc(msg, RDBD_FILE_MSG_SIZE(msg)); { struct stat filestat; if (stat(path, &filestat) == -1) { msg->msg[msg->header.pathlength] = RDBD_FTLE_CTRL_PATH_NONE; } else { if((filestat.st_mode & S_IFMT) == S_IFDIR) { msg->msg[msg->header.pathlength] = RDBD_FTLE_CTRL_PATH_IS_DIR; } else { msg->msg[msg->header.pathlength] = RDBD_FTLE_CTRL_PATH_IS_FILE; } } } file_service_request_send(&request_write_list, msg, 0); msg = RT_NULL; } if(RT_NULL != path) { rt_free(path); path = RT_NULL; } if(RT_NULL != msg) { rt_free(msg); msg = RT_NULL; } } } } if(!rt_list_isempty(&request_write_list)) //need write data to pc { if(FD_ISSET(file_service->out_pipe_write_fd, &writeset)) { request = rt_list_entry(request_write_list.next, struct file_request_send, list); result = write(file_service->out_pipe_write_fd, request->msg + request->msg_pos, RDBD_FILE_MSG_SIZE(request->msg) - request->msg_pos); if(result > 0) { request->msg_pos += result; } if(request->msg_pos == RDBD_FILE_MSG_SIZE(request->msg)) { request->msg_pos = 0; if(request->request_file_send && 0 != RDBD_FILE_MSG_MSG(request->msg)->header.length) { RDBD_FILE_MSG_MSG(request->msg)->header.offset += RDBD_FILE_MSG_MSG(request->msg)->header.length; path = rt_calloc(1,RDBD_FILE_MSG_MSG(request->msg)->header.pathlength + 1); if(RT_NULL == path) { LOG_E("No memory"); RT_ASSERT(0); } rt_strncpy(path, (const char *)RDBD_FILE_MSG_MSG(request->msg)->msg, RDBD_FILE_MSG_MSG(request->msg)->header.pathlength); path[RDBD_FILE_MSG_MSG(request->msg)->header.pathlength] = '\0'; request->msg = rt_realloc(request->msg, RDBD_FILE_MSG_MSG(request->msg)->header.pathlength + RDBD_FILE_READ_BLOCK_SIZE + RDBD_FILE_HEADER_SIZE); if(request->msg == RT_NULL) { LOG_E("No memory"); RT_ASSERT(0); } session = find_session(&session_list, path); if(session == RT_NULL) { LOG_E("Please open the file %s first", path); RT_ASSERT(0); } free(path); path = RT_NULL; RDBD_FILE_MSG_MSG(request->msg)->header.length = read_session(session, RDBD_FILE_MSG_MSG(request->msg)->header.offset, &RDBD_FILE_MSG_MSG(request->msg)->msg[RDBD_FILE_MSG_MSG(request->msg)->header.pathlength], RDBD_FILE_READ_BLOCK_SIZE); if(RDBD_FILE_MSG_MSG(request->msg)->header.length == 0) { RDBD_FILE_MSG_MSG(request->msg)->header.command = RDBD_FILE_CTRL_WRITE_DONE; request->request_file_send = 0; delete_session(session); session = RT_NULL; } } else { file_service_delete_request(request); } } } } } } } static int start(void * args); static int stop(void * args); static int resume(void * args); static int suspend(void * args); static struct rdbd_service_control_ops control_ops = { start, stop, resume, suspend, }; static int file_service_request_send(rt_list_t * header, struct rdbd_file_control_msg * msg, int request_file_send) { struct file_request_send * request; if(RT_NULL == header || RT_NULL == msg) { LOG_E("Write request arg error"); return -1; } request = rt_malloc(sizeof(struct file_request_send)); if(RT_NULL == request) { LOG_E("No memory request send to file"); return -1; } request->msg = (char *)msg; request->msg_pos = 0; request->request_file_send = request_file_send; rt_list_insert_before(header, &request->list); return 0; } static int file_service_delete_request(struct file_request_send * request) { if(RT_NULL == request) { LOG_E("Delete request arg error"); return -1; } if(RT_NULL != request->msg) { rt_free(request->msg); } rt_list_remove(&request->list); rt_free(request); return 0; } int rdbd_file_service_init(void) { struct rdbd_service * file_service; rdbd_t usbrdbd = rdbd_find("usb"); if(RT_NULL == usbrdbd) { LOG_E("rdbd usb find error"); return -1; } file_service = rdbd_create_service(RDBD_SERVICE_ID_FILE, "file", &control_ops, RT_NULL, "filein", 1024,"fileout", 1024, RDBD_SERVICE_FLAG_WR|RDBD_SERVICE_FLAG_RD); if(RT_NULL == file_service) { LOG_E("file_service create error"); goto _error; } LOG_I("Service %s created :", file_service->name); LOG_I("in_pipe_path %s", file_service->in_pipe_path); LOG_I("out_pipe_path %s", file_service->out_pipe_path); LOG_I("service_id %d", file_service->service_id); LOG_I("status %d", file_service->status); rdbd_service_install(usbrdbd, file_service); rdbd_service_control(file_service, RDBD_SERVICE_START, file_service); return 0; _error: if(RT_NULL != usbrdbd) { //TO DO } return -1; } INIT_APP_EXPORT(rdbd_file_service_init); static int start(void * args) { struct rdbd_service * file_service = (struct rdbd_service *)args; if(RT_NULL == file_service) { LOG_E("Start up service error, args is null"); return -1; } if(file_service->flag & RDBD_SERVICE_FLAG_RD) { file_service->in_pipe_read_fd = open(file_service->in_pipe_path, O_RDONLY | O_NONBLOCK, 0); if(file_service->in_pipe_read_fd < 0) { LOG_E("Start up service %s error open in pipe failed",file_service->name); return -1; } } if(file_service->flag & RDBD_SERVICE_FLAG_WR) { file_service->out_pipe_write_fd = open(file_service->out_pipe_path, O_WRONLY | O_NONBLOCK, 0); if(file_service->out_pipe_write_fd < 0) { LOG_E("Start up service %s error open out pipe failed",file_service->name); goto _error; } } if(file_service->flag & RDBD_SERVICE_FLAG_RD) { file_service->in_pipe_write_fd = open(file_service->in_pipe_path, O_WRONLY | O_NONBLOCK, 0); if(file_service->in_pipe_write_fd < 0) { LOG_E("Start up transfer error open in pipe failed",file_service->name); return -1; } } if(file_service->flag & RDBD_SERVICE_FLAG_WR) { file_service->out_pipe_read_fd = open(file_service->out_pipe_path, O_RDONLY | O_NONBLOCK, 0); if(file_service->out_pipe_read_fd < 0) { LOG_E("Start up transfer error open out pipe failed",file_service->name); goto _error; } } file_service->service_thread = rt_thread_create(file_service->name, file_service_thread_entry, file_service, 2048, 21, 20); if(RT_NULL != file_service->service_thread) { if(RT_EOK != rt_thread_startup(file_service->service_thread)) { rt_thread_delete(file_service->service_thread); file_service->service_thread = RT_NULL; goto _error; } } else { goto _error; } return 0; _error: stop(args); return -1; } static int stop(void * args) { struct rdbd_service * file_service = (struct rdbd_service *)args; if(RT_NULL == file_service) { LOG_E("Stop service error, args is null"); return -1; } if(RT_NULL != file_service->service_thread) { if(RT_EOK != rt_thread_delete(file_service->service_thread)) { LOG_E("Delete thread %s failed", file_service->service_thread->name); return -1; } file_service->service_thread = RT_NULL; } if(file_service->in_pipe_read_fd >= 0) { if(close(file_service->in_pipe_read_fd) < 0) { LOG_E("Close fd %d failed", file_service->in_pipe_read_fd); return -1; } file_service->in_pipe_read_fd = -1; } if(file_service->in_pipe_write_fd >= 0) { if(close(file_service->in_pipe_write_fd) < 0) { LOG_E("Close fd %d failed", file_service->in_pipe_write_fd); return -1; } file_service->in_pipe_write_fd = -1; } if(file_service->in_pipe_write_fd >= 0) { if(close(file_service->in_pipe_write_fd) < 0) { LOG_E("Close fd %d failed", file_service->in_pipe_write_fd); return -1; } file_service->in_pipe_write_fd = -1; } if(file_service->out_pipe_write_fd >= 0) { if(close(file_service->out_pipe_write_fd) < 0) { LOG_E("Close fd %d failed", file_service->out_pipe_write_fd); return -1; } file_service->out_pipe_write_fd = -1; } return 0; } static int resume(void * args) { //Not need return 0; } static int suspend(void * args) { //Not need return 0; } static rt_dstr_t * get_file_list(const char *pathname) { rt_dstr_t * list = RT_NULL, *fullpath = RT_NULL, *temp_list = RT_NULL; struct dirent * dir = NULL; DIR * root_dir = NULL; struct stat filestat; list = rt_dstr_new(""); root_dir = opendir(pathname); if(root_dir == NULL) { return list; } while(1) { dir = readdir(root_dir); if(dir == NULL) { break; } if (strncmp(dir->d_name, ".", 1) == 0) continue; /* skip hide file*/ if(strcmp("/", pathname) == 0) { fullpath = rt_dstr_sprintf(fullpath, "/%s", dir->d_name); } else { fullpath = rt_dstr_sprintf(fullpath, "%s/%s", pathname, dir->d_name); } if (stat(fullpath->str, &filestat) == -1) { LOG_E("cannot access the file %s", fullpath->str); return list; } if ((filestat.st_mode & S_IFMT) == S_IFDIR) { temp_list = get_file_list(fullpath->str); list = rt_dstr_append_printf(list, "%s", temp_list->str); rt_dstr_del(temp_list); temp_list = RT_NULL; } else { list = rt_dstr_append_printf(list, "%s\n", fullpath->str); } rt_dstr_del(fullpath); fullpath = RT_NULL; } closedir(root_dir); return list; } int list_dir(void) { int i = 0; rt_dstr_t * list = get_file_list("/"); while(list->str[i] != '\0') { rt_kprintf("%c",list->str[i++]); } rt_dstr_del(list); return 0; } MSH_CMD_EXPORT(list_dir, list dir); /* hash a single byte */ static rt_uint32_t fnv1a_r(unsigned char oneByte, rt_uint32_t hash) { return (oneByte ^ hash) * 0x01000193; // 0x01000193 = 16777619 } static rt_uint32_t rdbd_file_calc_hash(const char * filename) { FILE * fp = NULL; int ch; uint32_t hash = RDBD_FILE_HASH_FNV_SEED; fp = fopen(filename, "r"); if(fp == NULL) { LOG_W("%s not found!", filename); return 0; } while(1) { ch = fgetc(fp); if(ch == EOF) { break; } hash = fnv1a_r(ch, hash); } fclose(fp); return hash; }