mirror of
https://github.com/hathach/tinyusb.git
synced 2025-03-29 01:20:19 +00:00
486 lines
16 KiB
C
486 lines
16 KiB
C
/**************************************************************************/
|
|
/*!
|
|
@file msc_cli.c
|
|
@author hathach (tinyusb.org)
|
|
|
|
@section LICENSE
|
|
|
|
Software License Agreement (BSD License)
|
|
|
|
Copyright (c) 2013, hathach (tinyusb.org)
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
3. Neither the name of the copyright holders nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
This file is part of the tinyusb stack.
|
|
*/
|
|
/**************************************************************************/
|
|
|
|
#include "msc_cli.h"
|
|
#include "ctype.h"
|
|
|
|
#if CFG_TUH_MSC
|
|
|
|
#include "ff.h"
|
|
#include "diskio.h"
|
|
|
|
//--------------------------------------------------------------------+
|
|
// MACRO CONSTANT TYPEDEF
|
|
//--------------------------------------------------------------------+
|
|
#define CLI_MAX_BUFFER 256
|
|
#define CLI_FILE_READ_BUFFER (4*1024)
|
|
|
|
enum {
|
|
ASCII_BACKSPACE = 8,
|
|
};
|
|
|
|
typedef enum
|
|
{
|
|
CLI_ERROR_NONE = 0,
|
|
CLI_ERROR_INVALID_PARA,
|
|
CLI_ERROR_INVALID_PATH,
|
|
CLI_ERROR_FILE_EXISTED,
|
|
CLI_ERROR_FAILED
|
|
}cli_error_t;
|
|
|
|
static char const * const cli_error_message[] =
|
|
{
|
|
[CLI_ERROR_NONE ] = 0,
|
|
[CLI_ERROR_INVALID_PARA ] = "Invalid parameter(s)",
|
|
[CLI_ERROR_INVALID_PATH ] = "No such file or directory",
|
|
[CLI_ERROR_FILE_EXISTED ] = "file or directory already exists",
|
|
[CLI_ERROR_FAILED ] = "failed to execute"
|
|
};
|
|
|
|
//--------------------------------------------------------------------+
|
|
// CLI Database definition
|
|
//--------------------------------------------------------------------+
|
|
|
|
// command, function, description
|
|
#define CLI_COMMAND_TABLE(ENTRY) \
|
|
ENTRY(unknown , cli_cmd_unknown , NULL ) \
|
|
ENTRY(help , cli_cmd_help , NULL ) \
|
|
ENTRY(cls , cli_cmd_clear , "Clear the screen.\n cls\n" ) \
|
|
ENTRY(ls , cli_cmd_list , "List information of the FILEs.\n ls\n" ) \
|
|
ENTRY(cd , cli_cmd_changedir, "change the current directory.\n cd a_folder\n" ) \
|
|
ENTRY(cat , cli_cmd_cat , "display contents of a file.\n cat a_file.txt\n" ) \
|
|
ENTRY(cp , cli_cmd_copy , "Copies one or more files to another location.\n cp a_file.txt dir1/another_file.txt\n cp a_file.txt dir1\n" ) \
|
|
ENTRY(mkdir , cli_cmd_mkdir , "Create a DIRECTORY, if it does not already exist.\n mkdir <new folder>\n" ) \
|
|
ENTRY(mv , cli_cmd_move , "Rename or move a DIRECTORY or a FILE.\n mv old_name.txt new_name.txt\n mv old_name.txt dir1/new_name.txt\n" ) \
|
|
ENTRY(rm , cli_cmd_remove , "Remove (delete) an empty DIRECTORY or FILE.\n rm deleted_name.txt\n rm empty_dir\n" ) \
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Expands the function to have the standard function signature
|
|
//--------------------------------------------------------------------+
|
|
#define CLI_PROTOTYPE_EXPAND(command, function, description) \
|
|
cli_error_t function(char * p_para);
|
|
|
|
CLI_COMMAND_TABLE(CLI_PROTOTYPE_EXPAND)
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Expand to enum value
|
|
//--------------------------------------------------------------------+
|
|
#define CLI_ENUM_EXPAND(command, function, description) CLI_CMDTYPE_##command,
|
|
typedef enum
|
|
{
|
|
CLI_COMMAND_TABLE(CLI_ENUM_EXPAND)
|
|
CLI_CMDTYPE_COUNT
|
|
}cli_cmdtype_t;
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Expand to string table
|
|
//--------------------------------------------------------------------+
|
|
#define CLI_STRING_EXPAND(command, function, description) #command,
|
|
char const* const cli_string_tbl[] =
|
|
{
|
|
CLI_COMMAND_TABLE(CLI_STRING_EXPAND)
|
|
};
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Expand to Description table
|
|
//--------------------------------------------------------------------+
|
|
#define CLI_DESCRIPTION_EXPAND(command, function, description) description,
|
|
char const* const cli_description_tbl[] =
|
|
{
|
|
CLI_COMMAND_TABLE(CLI_DESCRIPTION_EXPAND)
|
|
};
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Expand to Command Lookup Table
|
|
//--------------------------------------------------------------------+
|
|
#define CMD_LOOKUP_EXPAND(command, function, description)\
|
|
[CLI_CMDTYPE_##command] = function,\
|
|
|
|
typedef cli_error_t (* const cli_cmdfunc_t)(char *);
|
|
static cli_cmdfunc_t cli_command_tbl[] =
|
|
{
|
|
CLI_COMMAND_TABLE(CMD_LOOKUP_EXPAND)
|
|
};
|
|
|
|
//--------------------------------------------------------------------+
|
|
// INTERNAL OBJECT & FUNCTION DECLARATION
|
|
//--------------------------------------------------------------------+
|
|
CFG_TUSB_MEM_SECTION uint8_t fileread_buffer[CLI_FILE_READ_BUFFER];
|
|
static char cli_buffer[CLI_MAX_BUFFER];
|
|
static char volume_label[20];
|
|
|
|
static inline void drive_number2letter(char * p_path)
|
|
{
|
|
if (p_path[1] == ':')
|
|
{
|
|
p_path[0] = 'E' + p_path[0] - '0' ;
|
|
}
|
|
}
|
|
|
|
static inline void drive_letter2number(char * p_path)
|
|
{
|
|
if (p_path[1] == ':')
|
|
{
|
|
p_path[0] = p_path[0] - 'E' + '0';
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
// IMPLEMENTATION
|
|
//--------------------------------------------------------------------+
|
|
// NOTES: prompt re-use cli_buffer --> should not be called when cli_buffer has contents
|
|
void cli_command_prompt(void)
|
|
{
|
|
f_getcwd(cli_buffer, CLI_MAX_BUFFER);
|
|
drive_number2letter(cli_buffer);
|
|
printf("\n%s %s\n$ ",
|
|
(volume_label[0] !=0) ? volume_label : "No Label",
|
|
cli_buffer);
|
|
|
|
tu_memclr(cli_buffer, CLI_MAX_BUFFER);
|
|
}
|
|
|
|
void cli_init(void)
|
|
{
|
|
tu_memclr(cli_buffer, CLI_MAX_BUFFER);
|
|
f_getlabel(NULL, volume_label, NULL);
|
|
cli_command_prompt();
|
|
}
|
|
|
|
void cli_poll(char ch)
|
|
{
|
|
if ( isprint(ch) )
|
|
{ // accumulate & echo
|
|
if (strlen(cli_buffer) < CLI_MAX_BUFFER)
|
|
{
|
|
cli_buffer[ strlen(cli_buffer) ] = ch;
|
|
putchar(ch);
|
|
}else
|
|
{
|
|
puts("cli buffer overflows");
|
|
tu_memclr(cli_buffer, CLI_MAX_BUFFER);
|
|
}
|
|
}
|
|
else if ( ch == ASCII_BACKSPACE && strlen(cli_buffer))
|
|
{
|
|
printf(ANSI_CURSOR_BACKWARD(1) ANSI_ERASE_LINE(0) ); // move cursor back & clear to the end of line
|
|
cli_buffer[ strlen(cli_buffer)-1 ] = 0;
|
|
}
|
|
else if ( ch == '\r')
|
|
{ // execute command
|
|
//------------- Separate Command & Parameter -------------//
|
|
putchar('\n');
|
|
char* p_space = strchr(cli_buffer, ' ');
|
|
uint32_t command_len = (p_space == NULL) ? strlen(cli_buffer) : (uint32_t) (p_space - cli_buffer);
|
|
char* p_para = (p_space == NULL) ? (cli_buffer+command_len) : (p_space+1); // point to NULL-character or after space
|
|
|
|
//------------- Find entered command in lookup table & execute it -------------//
|
|
uint8_t cmd_id;
|
|
for(cmd_id = CLI_CMDTYPE_COUNT - 1; cmd_id > CLI_CMDTYPE_unknown; cmd_id--)
|
|
{
|
|
if( 0 == strncmp(cli_buffer, cli_string_tbl[cmd_id], command_len) ) break;
|
|
}
|
|
|
|
cli_error_t error = cli_command_tbl[cmd_id]( p_para ); // command execution, (unknown command if cannot find)
|
|
|
|
if (CLI_ERROR_NONE != error) puts(cli_error_message[error]); // error message output if any
|
|
cli_command_prompt(); // print out current path
|
|
}
|
|
else if (ch=='\t') // \t may be used for auto-complete later
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// UNKNOWN Command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_unknown(char * p_para)
|
|
{
|
|
(void) p_para;
|
|
puts("unknown command, please type \"help\" for list of supported commands");
|
|
return CLI_ERROR_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// HELP command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_help(char * p_para)
|
|
{
|
|
(void) p_para;
|
|
|
|
puts("current supported commands are:");
|
|
for(uint8_t cmd_id = CLI_CMDTYPE_help+1; cmd_id < CLI_CMDTYPE_COUNT; cmd_id++)
|
|
{
|
|
printf("%s\t%s\n", cli_string_tbl[cmd_id], cli_description_tbl[cmd_id]);
|
|
}
|
|
|
|
return CLI_ERROR_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Clear Screen Command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_clear(char* p_para)
|
|
{
|
|
(void) p_para;
|
|
printf(ANSI_ERASE_SCREEN(2) ANSI_CURSOR_POSITION(1,1) );
|
|
return CLI_ERROR_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// LS Command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_list(char * p_para)
|
|
{
|
|
if ( strlen(p_para) == 0 ) // list current directory
|
|
{
|
|
DIR target_dir;
|
|
if ( FR_OK != f_opendir(&target_dir, ".") ) return CLI_ERROR_FAILED;
|
|
|
|
TCHAR long_filename[_MAX_LFN];
|
|
FILINFO dir_entry =
|
|
{
|
|
.lfname = long_filename,
|
|
.lfsize = _MAX_LFN
|
|
};
|
|
while( (f_readdir(&target_dir, &dir_entry) == FR_OK) && dir_entry.fname[0] != 0)
|
|
{
|
|
if ( dir_entry.fname[0] != '.' ) // ignore . and .. entry
|
|
{
|
|
TCHAR const * const p_name = (dir_entry.lfname[0] != 0) ? dir_entry.lfname : dir_entry.fname;
|
|
if ( dir_entry.fattrib & AM_DIR ) // directory
|
|
{
|
|
printf("/%s", p_name);
|
|
}else
|
|
{
|
|
printf("%-40s%d KB", p_name, dir_entry.fsize / 1000);
|
|
}
|
|
putchar('\n');
|
|
}
|
|
}
|
|
|
|
// (void) f_closedir(&target_dir);
|
|
}
|
|
else
|
|
{
|
|
puts("ls only supports list current directory only, try to cd to that folder first");
|
|
return CLI_ERROR_INVALID_PARA;
|
|
}
|
|
|
|
return CLI_ERROR_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// CD Command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_changedir(char * p_para)
|
|
{
|
|
if ( strlen(p_para) == 0 ) return CLI_ERROR_INVALID_PARA;
|
|
|
|
drive_letter2number(p_para);
|
|
|
|
if ( FR_OK != f_chdir(p_para) )
|
|
{
|
|
return CLI_ERROR_INVALID_PATH;
|
|
}
|
|
|
|
if ( p_para[1] == ':')
|
|
{ // path has drive letter --> change drive, update volume label
|
|
f_chdrive(p_para[0] - '0');
|
|
f_getlabel(NULL, volume_label, NULL);
|
|
}
|
|
|
|
return CLI_ERROR_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// CAT Command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_cat(char *p_para)
|
|
{
|
|
if ( strlen(p_para) == 0 ) return CLI_ERROR_INVALID_PARA;
|
|
|
|
FIL file;
|
|
|
|
switch( f_open(&file, p_para, FA_READ) )
|
|
{
|
|
case FR_OK:
|
|
{
|
|
uint32_t bytes_read = 0;
|
|
|
|
if ( (FR_OK == f_read(&file, fileread_buffer, CLI_FILE_READ_BUFFER, &bytes_read)) && (bytes_read > 0) )
|
|
{
|
|
if ( file.fsize < 0x80000 ) // ~ 500KB
|
|
{
|
|
putchar('\n');
|
|
do {
|
|
for(uint32_t i=0; i<bytes_read; i++) putchar( fileread_buffer[i] );
|
|
}while( (FR_OK == f_read(&file, fileread_buffer, CLI_FILE_READ_BUFFER, &bytes_read)) && (bytes_read > 0) );
|
|
}else
|
|
{ // not display file contents if first character is not printable (high chance of binary file)
|
|
printf("%s 's contents is too large\n", p_para);
|
|
}
|
|
}
|
|
f_close(&file);
|
|
}
|
|
break;
|
|
|
|
case FR_INVALID_NAME:
|
|
return CLI_ERROR_INVALID_PATH;
|
|
|
|
default :
|
|
return CLI_ERROR_FAILED;
|
|
}
|
|
|
|
return CLI_ERROR_NONE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// Make Directory command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_mkdir(char *p_para)
|
|
{
|
|
if ( strlen(p_para) == 0 ) return CLI_ERROR_INVALID_PARA;
|
|
|
|
return (f_mkdir(p_para) == FR_OK) ? CLI_ERROR_NONE : CLI_ERROR_FAILED;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// COPY command
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_copy(char *p_para)
|
|
{
|
|
char* p_space = strchr(p_para, ' ');
|
|
if ( p_space == NULL ) return CLI_ERROR_INVALID_PARA;
|
|
*p_space = 0; // replace space by NULL-character
|
|
|
|
char* p_dest = p_space+1;
|
|
if ( strlen(p_dest) == 0 ) return CLI_ERROR_INVALID_PARA;
|
|
|
|
drive_letter2number(p_para);
|
|
drive_letter2number(p_dest);
|
|
|
|
//------------- Check Existence of source file -------------//
|
|
FIL src_file;
|
|
if ( FR_OK != f_open(&src_file , p_para, FA_READ) ) return CLI_ERROR_INVALID_PATH;
|
|
|
|
//------------- Check if dest path is a folder or a non-existing file (overwritten is not allowed) -------------//
|
|
FILINFO dest_entry;
|
|
if ( (f_stat(p_dest, &dest_entry) == FR_OK) && (dest_entry.fattrib & AM_DIR) )
|
|
{ // the destination is an existed folder --> auto append dest filename to be the folder
|
|
strcat(p_dest, "/");
|
|
strcat(p_dest, p_para);
|
|
}
|
|
|
|
//------------- Open dest file and start the copy -------------//
|
|
cli_error_t error = CLI_ERROR_NONE;
|
|
FIL dest_file;
|
|
|
|
switch ( f_open(&dest_file, p_dest, FA_WRITE | FA_CREATE_NEW) )
|
|
{
|
|
case FR_EXIST:
|
|
error = CLI_ERROR_FILE_EXISTED;
|
|
break;
|
|
|
|
case FR_OK:
|
|
while(1) // copying
|
|
{
|
|
uint32_t bytes_read = 0;
|
|
uint32_t bytes_write = 0;
|
|
FRESULT res;
|
|
|
|
res = f_read(&src_file, fileread_buffer, CLI_FILE_READ_BUFFER, &bytes_read); /* Read a chunk of src file */
|
|
if ( (res != FR_OK) || (bytes_read == 0) ) break; /* error or eof */
|
|
|
|
res = f_write(&dest_file, fileread_buffer, bytes_read, &bytes_write); /* Write it to the dst file */
|
|
if ( (res != FR_OK) || (bytes_write < bytes_read) ) break; /* error or disk full */
|
|
}
|
|
|
|
f_close(&dest_file);
|
|
break;
|
|
|
|
default:
|
|
error = CLI_ERROR_FAILED;
|
|
break;
|
|
}
|
|
|
|
f_close(&src_file);
|
|
|
|
return error;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// MOVE/RENAME
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_move(char *p_para)
|
|
{
|
|
char* p_space = strchr(p_para, ' ');
|
|
if ( p_space == NULL ) return CLI_ERROR_INVALID_PARA;
|
|
*p_space = 0; // replace space by NULL-character
|
|
|
|
char* p_dest = p_space+1;
|
|
if ( strlen(p_dest) == 0 ) return CLI_ERROR_INVALID_PARA;
|
|
|
|
return (f_rename(p_para, p_dest) == FR_OK ) ? CLI_ERROR_NONE : CLI_ERROR_FAILED;
|
|
}
|
|
|
|
//--------------------------------------------------------------------+
|
|
// REMOVE
|
|
//--------------------------------------------------------------------+
|
|
cli_error_t cli_cmd_remove(char *p_para)
|
|
{
|
|
if ( strlen(p_para) == 0 ) return CLI_ERROR_INVALID_PARA;
|
|
|
|
switch( f_unlink(p_para) )
|
|
{
|
|
case FR_OK: return CLI_ERROR_NONE;
|
|
|
|
case FR_DENIED:
|
|
printf("cannot remove readonly file/foler or non-empty folder\n");
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
return CLI_ERROR_FAILED;
|
|
}
|
|
#endif
|