diff options
| author | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
| commit | 4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch) | |
| tree | c296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/xsystem.c | |
| download | relic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz | |
Diffstat (limited to 'src/xsystem.c')
| -rw-r--r-- | src/xsystem.c | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/src/xsystem.c b/src/xsystem.c new file mode 100644 index 0000000..4c7590c --- /dev/null +++ b/src/xsystem.c @@ -0,0 +1,614 @@ +/* +** Copyright (c) 2025 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** 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. +** +** Author contact information: +** drh@sqlite.org +** +******************************************************************************* +** +** This file contains code used to implement "fossil system ..." command. +** +** Fossil is frequently used by people familiar with Unix but who must +** sometimes also work on Windows systems. The "fossil sys ..." command +** provides a few work-arounds for command unix command-line utilities to +** help make development on Windows more habitable for long-time unix +** users. The commands provided here are normally cheap substitutes to +** their more feature-reach unix counterparts. But they are sufficient to +** get the job done. +** +** This source code file is called "xsystem.c" with the 'x' up front because +** if it were called "system.c", then makeheaders would generate a "system.h" +** header file, and that might be confused with an actual system header +** file. +*/ +#include "config.h" +#include "xsystem.h" +#include "qrf.h" +#include <time.h> +#ifdef _WIN32 +# include <windows.h> +#endif + + +/* Date and time */ +void xsystem_date(int argc, char **argv){ + (void)argc; + (void)argv; + fossil_print("%z = ", cgi_iso8601_datestamp()); + fossil_print("%z\n", cgi_rfc822_datestamp(time(0))); +} + +/* Present working directory */ +void xsystem_pwd(int argc, char **argv){ + char *zPwd = file_getcwd(0, 0); + fossil_print("%z\n", zPwd); +} + +/* Implement "stty size" */ +void xsystem_stty(int argc, char **argv){ + TerminalSize ts; + if( argc!=2 || strcmp(argv[1],"size")!=0 ){ + fossil_print("ERROR: only \"stty size\" is supported\n"); + }else{ + terminal_get_size(&ts); + fossil_print("%d %d\n", ts.nLines, ts.nColumns); + } +} + +/* Show where an executable is located on PATH */ +void xsystem_which(int argc, char **argv){ + int ePrint = 1; + int i; + for(i=1; i<argc; i++){ + const char *z = argv[i]; + if( z[0]!='-' ){ + fossil_app_on_path(z, ePrint); + }else{ + if( z[1]=='-' && z[2]!=0 ) z++; + if( fossil_strcmp(z,"-a")==0 ){ + ePrint = 2; + }else + { + fossil_fatal("unknown option \"%s\"", argv[i]); + } + } + } +} + +/* +** Bit values for the mFlags paramater to "ls" +*/ +#define LS_LONG 0x001 /* -l Long format - one object per line */ +#define LS_REVERSE 0x002 /* -r Reverse the sort order */ +#define LS_MTIME 0x004 /* -t Sort by mtime, newest first */ +#define LS_SIZE 0x008 /* -S Sort by size, largest first */ +#define LS_COMMA 0x010 /* -m Comma-separated list */ +#define LS_DIRONLY 0x020 /* -d Show just directory name, not content */ +#define LS_ALL 0x040 /* -a Show all entries */ +#define LS_COLOR 0x080 /* Colorize the output */ +#define LS_COLUMNS 0x100 /* -C Split column output */ + +/* xWrite() callback from QRF +*/ +static int xsystem_write(void *NotUsed, const char *zText, sqlite3_int64 n){ + fossil_puts(zText, 0, (int)n); + return SQLITE_OK; +} + +/* Helper function for xsystem_ls(): Make entries in the LS table +** for every file or directory zName. +** +** If zName is a directory, load all files contained within that directory. +** If zName is just a file, load only that file. +*/ +static void xsystem_ls_insert( + sqlite3_stmt *pStmt, + const char *zName, + int mFlags +){ + char *aList[2]; + char **azList; + int nList; + int i; + const char *zPrefix; + switch( file_isdir(zName, ExtFILE) ){ + case 1: { /* A directory */ + if( (mFlags & LS_DIRONLY)==0 ){ + int omitDots = (mFlags & LS_ALL)!=0 ? 2 : 1; + azList = 0; + nList = file_directory_list(zName, 0, omitDots, 0, &azList); + zPrefix = fossil_strcmp(zName,".") ? zName : 0; + break; + } + } + case 2: { /* A file */ + aList[0] = (char*)zName; + aList[1] = 0; + azList = aList; + nList = 1; + zPrefix = 0; + break; + } + default: { /* Does not exist */ + return; + } + } + for(i=0; i<nList; i++){ + char *zFile = zPrefix ? mprintf("%s/%s",zPrefix,azList[i]) : azList[i]; + int mode = file_mode(zFile, ExtFILE); + sqlite3_int64 sz = file_size(zFile, ExtFILE); + sqlite3_int64 mtime = file_mtime(zFile, ExtFILE); +#ifdef _WIN32 + if( (mFlags & LS_ALL)==0 ){ + wchar_t *zMbcs = fossil_utf8_to_path(zFile, 1); + DWORD attr = GetFileAttributesW(zMbcs); + fossil_path_free(zMbcs); + if( attr & FILE_ATTRIBUTE_HIDDEN ){ + if( zPrefix ) fossil_free(zFile); + continue; + } + } +#endif + sqlite3_bind_text(pStmt, 1, azList[i], -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(pStmt, 2, mtime); + sqlite3_bind_int64(pStmt, 3, sz); + sqlite3_bind_int(pStmt, 4, mode); + sqlite3_bind_int64(pStmt, 5, strlen(zFile)); + /* TODO: wcwidth()------^^^^^^ */ + sqlite3_step(pStmt); + sqlite3_reset(pStmt); + if( zPrefix ) fossil_free(zFile); + } + if( azList!=aList ){ + file_directory_list_free(azList); + } +} + +/* +** Return arguments to ORDER BY that will correctly sort the entries. +*/ +static const char *xsystem_ls_orderby(int mFlags){ + static const char *zSortTypes[] = { + "fn COLLATE NOCASE", + "mtime DESC", + "size DESC", + "fn COLLATE NOCASE DESC", + "mtime", + "size" + }; + int i = 0; + if( mFlags & LS_MTIME ) i = 1; + if( mFlags & LS_SIZE ) i = 2; + if( mFlags & LS_REVERSE ) i += 3; + return zSortTypes[i]; +} + +/* +** color(fn,mode) +** +** SQL function to colorize a filename based on its mode. +*/ +static void colorNameFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + int iMode = sqlite3_value_int(argv[1]); + sqlite3_str *pOut; + if( zName==0 ) return; + pOut = sqlite3_str_new(0); +#ifdef _WIN32 + if( sqlite3_strlike("%.exe",zName,0)==0 ) iMode |= 0111; +#endif + if( iMode & 040000 ){ + /* A directory */ + sqlite3_str_appendall(pOut, "\033[1;34m"); + }else if( iMode & 0100 ){ + /* Executable */ + sqlite3_str_appendall(pOut, "\033[1;32m"); + } + sqlite3_str_appendall(pOut, zName); + if( (iMode & 040100)!=0 ){ + sqlite3_str_appendall(pOut, "\033[0m"); + } + sqlite3_result_text(context, sqlite3_str_value(pOut), -1, SQLITE_TRANSIENT); + sqlite3_str_free(pOut); +} +/* Alternative implementation that does *not* introduce color */ +static void nocolorNameFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3_result_value(context, argv[0]); +} + + + +/* +** Show ls output information for content in the LS table +*/ +static void xsystem_ls_render( + sqlite3 *db, + int mFlags +){ + sqlite3_stmt *pStmt; + if( mFlags & LS_COLOR ){ + sqlite3_create_function(db, "color",2,SQLITE_UTF8,0,colorNameFunc,0,0); + }else{ + sqlite3_create_function(db, "color",2,SQLITE_UTF8,0,nocolorNameFunc,0,0); + } + if( (mFlags & LS_LONG)!=0 ){ + /* Long mode */ + char *zSql; + int szSz = 8; + sqlite3_prepare_v2(db, "SELECT length(max(size)) FROM ls", -1, &pStmt, 0); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + szSz = sqlite3_column_int(pStmt, 0); + } + sqlite3_finalize(pStmt); + pStmt = 0; + zSql = mprintf( + "SELECT mode, size, datetime(mtime,'unixepoch'), color(fn,mode)" + " FROM ls ORDER BY %s", + xsystem_ls_orderby(mFlags)); + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + char zMode[12]; + const char *zName = (const char*)sqlite3_column_text(pStmt, 3); + int mode = sqlite3_column_int(pStmt, 0); +#ifdef _WIN32 + memcpy(zMode, "-rw-", 5); + if( mode & 040000 ){ + zMode[0] = 'd'; + zMode[3] = 'x'; + }else if( sqlite3_strlike("%.EXE",zName,0)==0 ){ + zMode[3] = 'x'; + } +#else + memcpy(zMode, "----------", 11); + if( mode & 040000 ) zMode[0] = 'd'; + if( mode & 0400 ) zMode[1] = 'r'; + if( mode & 0200 ) zMode[2] = 'w'; + if( mode & 0100 ) zMode[3] = 'x'; + if( mode & 0040 ) zMode[4] = 'r'; + if( mode & 0020 ) zMode[5] = 'w'; + if( mode & 0010 ) zMode[6] = 'x'; + if( mode & 0004 ) zMode[7] = 'r'; + if( mode & 0002 ) zMode[8] = 'w'; + if( mode & 0001 ) zMode[9] = 'x'; +#endif + fossil_print("%s %*lld %s %s\n", + zMode, + szSz, + sqlite3_column_int64(pStmt, 1), + sqlite3_column_text(pStmt, 2), + zName); + } + sqlite3_finalize(pStmt); + }else if( (mFlags & LS_COMMA)!=0 ){ + /* Comma-separate list */ + int mx = terminal_get_width(80); + int sumW = 0; + char *zSql; + zSql = mprintf("SELECT color(fn,mode), dlen FROM ls ORDER BY %s", + xsystem_ls_orderby(mFlags)); + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *z = (const char*)sqlite3_column_text(pStmt, 0); + int w = sqlite3_column_int(pStmt, 1); + if( sumW==0 ){ + fossil_print("%s", z); + sumW = w; + }else if( sumW + w + 2 >= mx ){ + fossil_print("\n%s", z); + sumW = w; + }else{ + fossil_print(", %s", z); + sumW += w+2; + } + } + fossil_free(zSql); + sqlite3_finalize(pStmt); + if( sumW>0 ) fossil_print("\n"); + }else{ + /* Column mode with just filenames */ + sqlite3_qrf_spec spec; + char *zSql; + memset(&spec, 0, sizeof(spec)); + spec.iVersion = 1; + spec.xWrite = xsystem_write; + spec.eStyle = QRF_STYLE_Column; + spec.bTitles = QRF_No; + spec.eEsc = QRF_No; + if( mFlags & LS_COLUMNS ){ + spec.nScreenWidth = terminal_get_width(80); + spec.bSplitColumn = QRF_Yes; + } + zSql = mprintf("SELECT color(fn,mode) FROM ls ORDER BY %s", + xsystem_ls_orderby(mFlags)); + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + fossil_free(zSql); + sqlite3_format_query_result(pStmt, &spec, 0); + sqlite3_finalize(pStmt); + } + sqlite3_exec(db, "DELETE FROM ls;", 0, 0, 0); +} + +/* List files "ls" +** Options: +** +** -a Show files that begin with "." +** -C List by columns +** --color=WHEN Colorize output? +** -d Show just directory names, not content +** -l Long listing +** -m Comma-separated list +** -r Reverse sort +** -S Sort by size, largest first +** -t Sort by mtime, newest first +*/ +void xsystem_ls(int argc, char **argv){ + int i, rc; + sqlite3 *db; + sqlite3_stmt *pStmt = 0; + int mFlags = 0; + int nFile = 0; + int nDir = 0; + int bAutoColor = 1; + int needBlankLine = 0; + rc = sqlite3_open(":memory:", &db); + if( rc || db==0 ){ + fossil_fatal("Cannot open in-memory database"); + } + sqlite3_exec(db, "CREATE TABLE ls(fn,mtime,size,mode,dlen);", 0,0,0); + rc = sqlite3_prepare_v2(db, "INSERT INTO ls VALUES(?1,?2,?3,?4,?5)", + -1, &pStmt, 0); + if( rc || db==0 ){ + fossil_fatal("Cannot prepare INSERT statement"); + } + for(i=1; i<argc; i++){ + const char *z = argv[i]; + if( z[0]=='-' ){ + if( z[1]=='-' ){ + if( strncmp(z,"--color",7)==0 ){ + if( z[7]==0 || strcmp(&z[7],"=always")==0 ){ + mFlags |= LS_COLOR; + }else if( strcmp(&z[7],"=never")==0 ){ + bAutoColor = 0; + } + }else{ + fossil_fatal("unknown option: %s", z); + } + }else{ + int k; + for(k=1; z[k]; k++){ + switch( z[k] ){ + case 'a': mFlags |= LS_ALL; break; + case 'd': mFlags |= LS_DIRONLY; break; + case 'l': mFlags |= LS_LONG; break; + case 'm': mFlags |= LS_COMMA; break; + case 'r': mFlags |= LS_REVERSE; break; + case 'S': mFlags |= LS_SIZE; break; + case 't': mFlags |= LS_MTIME; break; + case 'C': mFlags |= LS_COLUMNS; break; + default: { + fossil_fatal("unknown option: -%c", z[k]); + } + } + } + } + }else{ + if( (mFlags & LS_DIRONLY)==0 && file_isdir(z, ExtFILE)==1 ){ + nDir++; + }else{ + nFile++; + xsystem_ls_insert(pStmt, z, mFlags); + } + } + } + if( fossil_isatty(1) ){ + if( bAutoColor && terminal_is_vt100() ) mFlags |= LS_COLOR; + mFlags |= LS_COLUMNS; + } + if( nFile>0 ){ + xsystem_ls_render(db, mFlags); + needBlankLine = 1; + }else if( nDir==0 ){ + xsystem_ls_insert(pStmt, ".", mFlags); + xsystem_ls_render(db, mFlags); + } + if( nDir>0 ){ + for(i=1; i<argc; i++){ + const char *z = argv[i]; + if( z[0]=='-' ) continue; + if( file_isdir(z, ExtFILE)!=1 ) continue; + if( needBlankLine ){ + fossil_print("\n"); + needBlankLine = 0; + } + fossil_print("%s:\n", z); + xsystem_ls_insert(pStmt, z, mFlags); + xsystem_ls_render(db, mFlags); + } + } + sqlite3_finalize(pStmt); + sqlite3_close(db); +} + +/* +** unzip [-l] ZIPFILE +*/ +void xsystem_unzip(int argc, char **argv){ + const char *zZipfile = 0; + int doList = 0; + int i; + char *a[5]; + int n; + extern int sqlite3_shell(int, char**); + + for(i=1; i<argc; i++){ + const char *z = argv[i]; + if( z[0]=='-' ){ + if( z[1]=='-' && z[2]!=0 ) z++; + if( strcmp(z,"-l")==0 ){ + doList = 1; + }else + { + fossil_fatal("unknown option: %s", argv[i]); + } + }else if( zZipfile!=0 ){ + fossil_fatal("extra argument: %s", z); + }else{ + zZipfile = z; + } + } + if( zZipfile==0 ){ + fossil_fatal("Usage: fossil sys unzip [-l] ZIPFILE"); + }else if( file_size(zZipfile, ExtFILE)<0 ){ + fossil_fatal("No such file: %s\n", zZipfile); + } + g.zRepositoryName = 0; + g.zLocalDbName = 0; + g.zConfigDbName = 0; + sqlite3_shutdown(); + a[0] = argv[0]; + a[1] = (char*)zZipfile; + if( doList ){ + a[2] = ".mode column"; + a[3] = "SELECT sz AS Size, date(mtime,'unixepoch') AS Date," + " time(mtime,'unixepoch') AS Time," + " if(((mode>>12)&15)=10,name||' -> '||data,name) AS Name" + " FROM zip;"; + n = 4; + }else{ + a[2] = ".mode list"; + a[3] = "SELECT if(((mode>>12)&15)==10,'symlink-ignored: '||name," + "writefile(name,data,mode,mtime) IS NULL," + "'error: '||name,'extracting: '||name) FROM zip"; + n = 4; + } + a[n] = 0; + sqlite3_shell(n,a); +} + +/* +** zip [OPTIONS] ZIPFILE FILE ... +*/ +void xsystem_zip(int argc, char **argv){ + int i; + for(i=0; i<argc; i++){ + g.argv[i+1] = argv[i]; + } + g.argc = argc+1; + filezip_cmd(); +} + +/* +** Available system commands. +*/ +typedef struct XSysCmd XSysCmd; +static struct XSysCmd { + const char *zName; + void (*xFunc)(int,char**); + const char *zHelp; +} aXSysCmd[] = { + { "date", xsystem_date, + "\n" + "Show the current system time and date\n" + }, + { "ls", xsystem_ls, + "[OPTIONS] [PATH] ...\n" + "Options:\n" + " -a Show files that begin with '.'\n" + " -C Split columns\n" + " -d Show just directory names, not content\n" + " -l Long listing\n" + " -m Comma-separated list\n" + " -r Reverse sort order\n" + " -S Sort by size, largest first\n" + " -t Sort by mtime, newest first\n" + " --color[=WHEN] Colorize output?\n" + }, + { "pwd", xsystem_pwd, + "\n" + "Show the Present Working Directory name\n" + }, + { "stty", xsystem_stty, + "\n" + "Show the size of the TTY\n" + }, + { "unzip", xsystem_unzip, + "[-l] ZIPFILE\n\n" + "Extract content from ZIPFILE, or list the content if the -l option\n" + "is used.\n" + }, + { "which", xsystem_which, + "EXE ...\n" + "Show the location on PATH of executables EXE\n" + "Options:\n" + " -a Show all path locations rather than just the first\n" + }, + { "zip", xsystem_zip, + "ZIPFILE FILE ...\n\n" + "Create a new ZIP archive named ZIPFILE using listed files as content\n" + }, +}; + +/* +** COMMAND: system +** +** Usage: %fossil system COMMAND ARGS... +** +** Often abbreviated as just "fossil sys", this command provides primitive, +** low-level unix-like commands for use on systems that lack those commands +** natively. +** +** Type "fossil sys help" for a list of available commands. +** +** Type "fossil sys help COMMAND" for detailed help on a particular +** command. +*/ +void xsystem_cmd(void){ + int i; + const char *zCmd; + int bHelp = 0; + if( g.argc<=2 || (g.argc==3 && fossil_strcmp(g.argv[2],"help")==0) ){ + fossil_print("Available commands:\n"); + for(i=0; i<count(aXSysCmd); i++){ + if( (i%4)==3 || i==count(aXSysCmd)-1 ){ + fossil_print(" %s\n", aXSysCmd[i].zName); + }else{ + fossil_print(" %-12s", aXSysCmd[i].zName); + } + } + return; + } + zCmd = g.argv[2]; + if( fossil_strcmp(zCmd, "help")==0 ){ + bHelp = 1; + zCmd = g.argv[3]; + } + for(i=0; i<count(aXSysCmd); i++){ + if( fossil_strcmp(zCmd,aXSysCmd[i].zName)==0 ){ + if( !bHelp ){ + aXSysCmd[i].xFunc(g.argc-2, g.argv+2); + }else{ + fossil_print("Usage: fossil system %s %s", zCmd, aXSysCmd[i].zHelp); + } + return; + } + } + fossil_fatal("Unknown system command \"%s\"." + " Use \"%s system help\" for a list of available commands", + zCmd, g.argv[0]); +} |
