Fossil SCM

fossil-scm / src / xsystem.c
Blame History Raw 615 lines
1
/*
2
** Copyright (c) 2025 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
**
15
*******************************************************************************
16
**
17
** This file contains code used to implement "fossil system ..." command.
18
**
19
** Fossil is frequently used by people familiar with Unix but who must
20
** sometimes also work on Windows systems. The "fossil sys ..." command
21
** provides a few work-arounds for command unix command-line utilities to
22
** help make development on Windows more habitable for long-time unix
23
** users. The commands provided here are normally cheap substitutes to
24
** their more feature-reach unix counterparts. But they are sufficient to
25
** get the job done.
26
**
27
** This source code file is called "xsystem.c" with the 'x' up front because
28
** if it were called "system.c", then makeheaders would generate a "system.h"
29
** header file, and that might be confused with an actual system header
30
** file.
31
*/
32
#include "config.h"
33
#include "xsystem.h"
34
#include "qrf.h"
35
#include <time.h>
36
#ifdef _WIN32
37
# include <windows.h>
38
#endif
39
40
41
/* Date and time */
42
void xsystem_date(int argc, char **argv){
43
(void)argc;
44
(void)argv;
45
fossil_print("%z = ", cgi_iso8601_datestamp());
46
fossil_print("%z\n", cgi_rfc822_datestamp(time(0)));
47
}
48
49
/* Present working directory */
50
void xsystem_pwd(int argc, char **argv){
51
char *zPwd = file_getcwd(0, 0);
52
fossil_print("%z\n", zPwd);
53
}
54
55
/* Implement "stty size" */
56
void xsystem_stty(int argc, char **argv){
57
TerminalSize ts;
58
if( argc!=2 || strcmp(argv[1],"size")!=0 ){
59
fossil_print("ERROR: only \"stty size\" is supported\n");
60
}else{
61
terminal_get_size(&ts);
62
fossil_print("%d %d\n", ts.nLines, ts.nColumns);
63
}
64
}
65
66
/* Show where an executable is located on PATH */
67
void xsystem_which(int argc, char **argv){
68
int ePrint = 1;
69
int i;
70
for(i=1; i<argc; i++){
71
const char *z = argv[i];
72
if( z[0]!='-' ){
73
fossil_app_on_path(z, ePrint);
74
}else{
75
if( z[1]=='-' && z[2]!=0 ) z++;
76
if( fossil_strcmp(z,"-a")==0 ){
77
ePrint = 2;
78
}else
79
{
80
fossil_fatal("unknown option \"%s\"", argv[i]);
81
}
82
}
83
}
84
}
85
86
/*
87
** Bit values for the mFlags paramater to "ls"
88
*/
89
#define LS_LONG 0x001 /* -l Long format - one object per line */
90
#define LS_REVERSE 0x002 /* -r Reverse the sort order */
91
#define LS_MTIME 0x004 /* -t Sort by mtime, newest first */
92
#define LS_SIZE 0x008 /* -S Sort by size, largest first */
93
#define LS_COMMA 0x010 /* -m Comma-separated list */
94
#define LS_DIRONLY 0x020 /* -d Show just directory name, not content */
95
#define LS_ALL 0x040 /* -a Show all entries */
96
#define LS_COLOR 0x080 /* Colorize the output */
97
#define LS_COLUMNS 0x100 /* -C Split column output */
98
99
/* xWrite() callback from QRF
100
*/
101
static int xsystem_write(void *NotUsed, const char *zText, sqlite3_int64 n){
102
fossil_puts(zText, 0, (int)n);
103
return SQLITE_OK;
104
}
105
106
/* Helper function for xsystem_ls(): Make entries in the LS table
107
** for every file or directory zName.
108
**
109
** If zName is a directory, load all files contained within that directory.
110
** If zName is just a file, load only that file.
111
*/
112
static void xsystem_ls_insert(
113
sqlite3_stmt *pStmt,
114
const char *zName,
115
int mFlags
116
){
117
char *aList[2];
118
char **azList;
119
int nList;
120
int i;
121
const char *zPrefix;
122
switch( file_isdir(zName, ExtFILE) ){
123
case 1: { /* A directory */
124
if( (mFlags & LS_DIRONLY)==0 ){
125
int omitDots = (mFlags & LS_ALL)!=0 ? 2 : 1;
126
azList = 0;
127
nList = file_directory_list(zName, 0, omitDots, 0, &azList);
128
zPrefix = fossil_strcmp(zName,".") ? zName : 0;
129
break;
130
}
131
}
132
case 2: { /* A file */
133
aList[0] = (char*)zName;
134
aList[1] = 0;
135
azList = aList;
136
nList = 1;
137
zPrefix = 0;
138
break;
139
}
140
default: { /* Does not exist */
141
return;
142
}
143
}
144
for(i=0; i<nList; i++){
145
char *zFile = zPrefix ? mprintf("%s/%s",zPrefix,azList[i]) : azList[i];
146
int mode = file_mode(zFile, ExtFILE);
147
sqlite3_int64 sz = file_size(zFile, ExtFILE);
148
sqlite3_int64 mtime = file_mtime(zFile, ExtFILE);
149
#ifdef _WIN32
150
if( (mFlags & LS_ALL)==0 ){
151
wchar_t *zMbcs = fossil_utf8_to_path(zFile, 1);
152
DWORD attr = GetFileAttributesW(zMbcs);
153
fossil_path_free(zMbcs);
154
if( attr & FILE_ATTRIBUTE_HIDDEN ){
155
if( zPrefix ) fossil_free(zFile);
156
continue;
157
}
158
}
159
#endif
160
sqlite3_bind_text(pStmt, 1, azList[i], -1, SQLITE_TRANSIENT);
161
sqlite3_bind_int64(pStmt, 2, mtime);
162
sqlite3_bind_int64(pStmt, 3, sz);
163
sqlite3_bind_int(pStmt, 4, mode);
164
sqlite3_bind_int64(pStmt, 5, strlen(zFile));
165
/* TODO: wcwidth()------^^^^^^ */
166
sqlite3_step(pStmt);
167
sqlite3_reset(pStmt);
168
if( zPrefix ) fossil_free(zFile);
169
}
170
if( azList!=aList ){
171
file_directory_list_free(azList);
172
}
173
}
174
175
/*
176
** Return arguments to ORDER BY that will correctly sort the entries.
177
*/
178
static const char *xsystem_ls_orderby(int mFlags){
179
static const char *zSortTypes[] = {
180
"fn COLLATE NOCASE",
181
"mtime DESC",
182
"size DESC",
183
"fn COLLATE NOCASE DESC",
184
"mtime",
185
"size"
186
};
187
int i = 0;
188
if( mFlags & LS_MTIME ) i = 1;
189
if( mFlags & LS_SIZE ) i = 2;
190
if( mFlags & LS_REVERSE ) i += 3;
191
return zSortTypes[i];
192
}
193
194
/*
195
** color(fn,mode)
196
**
197
** SQL function to colorize a filename based on its mode.
198
*/
199
static void colorNameFunc(
200
sqlite3_context *context,
201
int argc,
202
sqlite3_value **argv
203
){
204
const char *zName = (const char*)sqlite3_value_text(argv[0]);
205
int iMode = sqlite3_value_int(argv[1]);
206
sqlite3_str *pOut;
207
if( zName==0 ) return;
208
pOut = sqlite3_str_new(0);
209
#ifdef _WIN32
210
if( sqlite3_strlike("%.exe",zName,0)==0 ) iMode |= 0111;
211
#endif
212
if( iMode & 040000 ){
213
/* A directory */
214
sqlite3_str_appendall(pOut, "\033[1;34m");
215
}else if( iMode & 0100 ){
216
/* Executable */
217
sqlite3_str_appendall(pOut, "\033[1;32m");
218
}
219
sqlite3_str_appendall(pOut, zName);
220
if( (iMode & 040100)!=0 ){
221
sqlite3_str_appendall(pOut, "\033[0m");
222
}
223
sqlite3_result_text(context, sqlite3_str_value(pOut), -1, SQLITE_TRANSIENT);
224
sqlite3_str_free(pOut);
225
}
226
/* Alternative implementation that does *not* introduce color */
227
static void nocolorNameFunc(
228
sqlite3_context *context,
229
int argc,
230
sqlite3_value **argv
231
){
232
sqlite3_result_value(context, argv[0]);
233
}
234
235
236
237
/*
238
** Show ls output information for content in the LS table
239
*/
240
static void xsystem_ls_render(
241
sqlite3 *db,
242
int mFlags
243
){
244
sqlite3_stmt *pStmt;
245
if( mFlags & LS_COLOR ){
246
sqlite3_create_function(db, "color",2,SQLITE_UTF8,0,colorNameFunc,0,0);
247
}else{
248
sqlite3_create_function(db, "color",2,SQLITE_UTF8,0,nocolorNameFunc,0,0);
249
}
250
if( (mFlags & LS_LONG)!=0 ){
251
/* Long mode */
252
char *zSql;
253
int szSz = 8;
254
sqlite3_prepare_v2(db, "SELECT length(max(size)) FROM ls", -1, &pStmt, 0);
255
if( sqlite3_step(pStmt)==SQLITE_ROW ){
256
szSz = sqlite3_column_int(pStmt, 0);
257
}
258
sqlite3_finalize(pStmt);
259
pStmt = 0;
260
zSql = mprintf(
261
"SELECT mode, size, datetime(mtime,'unixepoch'), color(fn,mode)"
262
" FROM ls ORDER BY %s",
263
xsystem_ls_orderby(mFlags));
264
sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
265
while( sqlite3_step(pStmt)==SQLITE_ROW ){
266
char zMode[12];
267
const char *zName = (const char*)sqlite3_column_text(pStmt, 3);
268
int mode = sqlite3_column_int(pStmt, 0);
269
#ifdef _WIN32
270
memcpy(zMode, "-rw-", 5);
271
if( mode & 040000 ){
272
zMode[0] = 'd';
273
zMode[3] = 'x';
274
}else if( sqlite3_strlike("%.EXE",zName,0)==0 ){
275
zMode[3] = 'x';
276
}
277
#else
278
memcpy(zMode, "----------", 11);
279
if( mode & 040000 ) zMode[0] = 'd';
280
if( mode & 0400 ) zMode[1] = 'r';
281
if( mode & 0200 ) zMode[2] = 'w';
282
if( mode & 0100 ) zMode[3] = 'x';
283
if( mode & 0040 ) zMode[4] = 'r';
284
if( mode & 0020 ) zMode[5] = 'w';
285
if( mode & 0010 ) zMode[6] = 'x';
286
if( mode & 0004 ) zMode[7] = 'r';
287
if( mode & 0002 ) zMode[8] = 'w';
288
if( mode & 0001 ) zMode[9] = 'x';
289
#endif
290
fossil_print("%s %*lld %s %s\n",
291
zMode,
292
szSz,
293
sqlite3_column_int64(pStmt, 1),
294
sqlite3_column_text(pStmt, 2),
295
zName);
296
}
297
sqlite3_finalize(pStmt);
298
}else if( (mFlags & LS_COMMA)!=0 ){
299
/* Comma-separate list */
300
int mx = terminal_get_width(80);
301
int sumW = 0;
302
char *zSql;
303
zSql = mprintf("SELECT color(fn,mode), dlen FROM ls ORDER BY %s",
304
xsystem_ls_orderby(mFlags));
305
sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
306
while( sqlite3_step(pStmt)==SQLITE_ROW ){
307
const char *z = (const char*)sqlite3_column_text(pStmt, 0);
308
int w = sqlite3_column_int(pStmt, 1);
309
if( sumW==0 ){
310
fossil_print("%s", z);
311
sumW = w;
312
}else if( sumW + w + 2 >= mx ){
313
fossil_print("\n%s", z);
314
sumW = w;
315
}else{
316
fossil_print(", %s", z);
317
sumW += w+2;
318
}
319
}
320
fossil_free(zSql);
321
sqlite3_finalize(pStmt);
322
if( sumW>0 ) fossil_print("\n");
323
}else{
324
/* Column mode with just filenames */
325
sqlite3_qrf_spec spec;
326
char *zSql;
327
memset(&spec, 0, sizeof(spec));
328
spec.iVersion = 1;
329
spec.xWrite = xsystem_write;
330
spec.eStyle = QRF_STYLE_Column;
331
spec.bTitles = QRF_No;
332
spec.eEsc = QRF_No;
333
if( mFlags & LS_COLUMNS ){
334
spec.nScreenWidth = terminal_get_width(80);
335
spec.bSplitColumn = QRF_Yes;
336
}
337
zSql = mprintf("SELECT color(fn,mode) FROM ls ORDER BY %s",
338
xsystem_ls_orderby(mFlags));
339
sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
340
fossil_free(zSql);
341
sqlite3_format_query_result(pStmt, &spec, 0);
342
sqlite3_finalize(pStmt);
343
}
344
sqlite3_exec(db, "DELETE FROM ls;", 0, 0, 0);
345
}
346
347
/* List files "ls"
348
** Options:
349
**
350
** -a Show files that begin with "."
351
** -C List by columns
352
** --color=WHEN Colorize output?
353
** -d Show just directory names, not content
354
** -l Long listing
355
** -m Comma-separated list
356
** -r Reverse sort
357
** -S Sort by size, largest first
358
** -t Sort by mtime, newest first
359
*/
360
void xsystem_ls(int argc, char **argv){
361
int i, rc;
362
sqlite3 *db;
363
sqlite3_stmt *pStmt = 0;
364
int mFlags = 0;
365
int nFile = 0;
366
int nDir = 0;
367
int bAutoColor = 1;
368
int needBlankLine = 0;
369
rc = sqlite3_open(":memory:", &db);
370
if( rc || db==0 ){
371
fossil_fatal("Cannot open in-memory database");
372
}
373
sqlite3_exec(db, "CREATE TABLE ls(fn,mtime,size,mode,dlen);", 0,0,0);
374
rc = sqlite3_prepare_v2(db, "INSERT INTO ls VALUES(?1,?2,?3,?4,?5)",
375
-1, &pStmt, 0);
376
if( rc || db==0 ){
377
fossil_fatal("Cannot prepare INSERT statement");
378
}
379
for(i=1; i<argc; i++){
380
const char *z = argv[i];
381
if( z[0]=='-' ){
382
if( z[1]=='-' ){
383
if( strncmp(z,"--color",7)==0 ){
384
if( z[7]==0 || strcmp(&z[7],"=always")==0 ){
385
mFlags |= LS_COLOR;
386
}else if( strcmp(&z[7],"=never")==0 ){
387
bAutoColor = 0;
388
}
389
}else{
390
fossil_fatal("unknown option: %s", z);
391
}
392
}else{
393
int k;
394
for(k=1; z[k]; k++){
395
switch( z[k] ){
396
case 'a': mFlags |= LS_ALL; break;
397
case 'd': mFlags |= LS_DIRONLY; break;
398
case 'l': mFlags |= LS_LONG; break;
399
case 'm': mFlags |= LS_COMMA; break;
400
case 'r': mFlags |= LS_REVERSE; break;
401
case 'S': mFlags |= LS_SIZE; break;
402
case 't': mFlags |= LS_MTIME; break;
403
case 'C': mFlags |= LS_COLUMNS; break;
404
default: {
405
fossil_fatal("unknown option: -%c", z[k]);
406
}
407
}
408
}
409
}
410
}else{
411
if( (mFlags & LS_DIRONLY)==0 && file_isdir(z, ExtFILE)==1 ){
412
nDir++;
413
}else{
414
nFile++;
415
xsystem_ls_insert(pStmt, z, mFlags);
416
}
417
}
418
}
419
if( fossil_isatty(1) ){
420
if( bAutoColor ) mFlags |= LS_COLOR;
421
mFlags |= LS_COLUMNS;
422
}
423
if( nFile>0 ){
424
xsystem_ls_render(db, mFlags);
425
needBlankLine = 1;
426
}else if( nDir==0 ){
427
xsystem_ls_insert(pStmt, ".", mFlags);
428
xsystem_ls_render(db, mFlags);
429
}
430
if( nDir>0 ){
431
for(i=1; i<argc; i++){
432
const char *z = argv[i];
433
if( z[0]=='-' ) continue;
434
if( file_isdir(z, ExtFILE)!=1 ) continue;
435
if( needBlankLine ){
436
fossil_print("\n");
437
needBlankLine = 0;
438
}
439
fossil_print("%s:\n", z);
440
xsystem_ls_insert(pStmt, z, mFlags);
441
xsystem_ls_render(db, mFlags);
442
}
443
}
444
sqlite3_finalize(pStmt);
445
sqlite3_close(db);
446
}
447
448
/*
449
** unzip [-l] ZIPFILE
450
*/
451
void xsystem_unzip(int argc, char **argv){
452
const char *zZipfile = 0;
453
int doList = 0;
454
int i;
455
char *a[5];
456
int n;
457
extern int sqlite3_shell(int, char**);
458
459
for(i=1; i<argc; i++){
460
const char *z = argv[i];
461
if( z[0]=='-' ){
462
if( z[1]=='-' && z[2]!=0 ) z++;
463
if( strcmp(z,"-l")==0 ){
464
doList = 1;
465
}else
466
{
467
fossil_fatal("unknown option: %s", argv[i]);
468
}
469
}else if( zZipfile!=0 ){
470
fossil_fatal("extra argument: %s", z);
471
}else{
472
zZipfile = z;
473
}
474
}
475
if( zZipfile==0 ){
476
fossil_fatal("Usage: fossil sys unzip [-l] ZIPFILE");
477
}else if( file_size(zZipfile, ExtFILE)<0 ){
478
fossil_fatal("No such file: %s\n", zZipfile);
479
}
480
g.zRepositoryName = 0;
481
g.zLocalDbName = 0;
482
g.zConfigDbName = 0;
483
sqlite3_shutdown();
484
a[0] = argv[0];
485
a[1] = (char*)zZipfile;
486
if( doList ){
487
a[2] = ".mode column";
488
a[3] = "SELECT sz AS Size, date(mtime,'unixepoch') AS Date,"
489
" time(mtime,'unixepoch') AS Time,"
490
" if(((mode>>12)&15)=10,name||' -> '||data,name) AS Name"
491
" FROM zip;";
492
n = 4;
493
}else{
494
a[2] = ".mode list";
495
a[3] = "SELECT if(((mode>>12)&15)==10,'symlink-ignored: '||name,"
496
"writefile(name,data,mode,mtime) IS NULL,"
497
"'error: '||name,'extracting: '||name) FROM zip";
498
n = 4;
499
}
500
a[n] = 0;
501
sqlite3_shell(n,a);
502
}
503
504
/*
505
** zip [OPTIONS] ZIPFILE FILE ...
506
*/
507
void xsystem_zip(int argc, char **argv){
508
int i;
509
for(i=0; i<argc; i++){
510
g.argv[i+1] = argv[i];
511
}
512
g.argc = argc+1;
513
filezip_cmd();
514
}
515
516
/*
517
** Available system commands.
518
*/
519
typedef struct XSysCmd XSysCmd;
520
static struct XSysCmd {
521
const char *zName;
522
void (*xFunc)(int,char**);
523
const char *zHelp;
524
} aXSysCmd[] = {
525
{ "date", xsystem_date,
526
"\n"
527
"Show the current system time and date\n"
528
},
529
{ "ls", xsystem_ls,
530
"[OPTIONS] [PATH] ...\n"
531
"Options:\n"
532
" -a Show files that begin with '.'\n"
533
" -C Split columns\n"
534
" -d Show just directory names, not content\n"
535
" -l Long listing\n"
536
" -m Comma-separated list\n"
537
" -r Reverse sort order\n"
538
" -S Sort by size, largest first\n"
539
" -t Sort by mtime, newest first\n"
540
" --color[=WHEN] Colorize output?\n"
541
},
542
{ "pwd", xsystem_pwd,
543
"\n"
544
"Show the Present Working Directory name\n"
545
},
546
{ "stty", xsystem_stty,
547
"\n"
548
"Show the size of the TTY\n"
549
},
550
{ "unzip", xsystem_unzip,
551
"[-l] ZIPFILE\n\n"
552
"Extract content from ZIPFILE, or list the content if the -l option\n"
553
"is used.\n"
554
},
555
{ "which", xsystem_which,
556
"EXE ...\n"
557
"Show the location on PATH of executables EXE\n"
558
"Options:\n"
559
" -a Show all path locations rather than just the first\n"
560
},
561
{ "zip", xsystem_zip,
562
"ZIPFILE FILE ...\n\n"
563
"Create a new ZIP archive named ZIPFILE using listed files as content\n"
564
},
565
};
566
567
/*
568
** COMMAND: system
569
**
570
** Usage: %fossil system COMMAND ARGS...
571
**
572
** Often abbreviated as just "fossil sys", this command provides primitive,
573
** low-level unix-like commands for use on systems that lack those commands
574
** natively.
575
**
576
** Type "fossil sys help" for a list of available commands.
577
**
578
** Type "fossil sys help COMMAND" for detailed help on a particular
579
** command.
580
*/
581
void xsystem_cmd(void){
582
int i;
583
const char *zCmd;
584
int bHelp = 0;
585
if( g.argc<=2 || (g.argc==3 && fossil_strcmp(g.argv[2],"help")==0) ){
586
fossil_print("Available commands:\n");
587
for(i=0; i<count(aXSysCmd); i++){
588
if( (i%4)==3 || i==count(aXSysCmd)-1 ){
589
fossil_print(" %s\n", aXSysCmd[i].zName);
590
}else{
591
fossil_print(" %-12s", aXSysCmd[i].zName);
592
}
593
}
594
return;
595
}
596
zCmd = g.argv[2];
597
if( fossil_strcmp(zCmd, "help")==0 ){
598
bHelp = 1;
599
zCmd = g.argv[3];
600
}
601
for(i=0; i<count(aXSysCmd); i++){
602
if( fossil_strcmp(zCmd,aXSysCmd[i].zName)==0 ){
603
if( !bHelp ){
604
aXSysCmd[i].xFunc(g.argc-2, g.argv+2);
605
}else{
606
fossil_print("Usage: fossil system %s %s", zCmd, aXSysCmd[i].zHelp);
607
}
608
return;
609
}
610
}
611
fossil_fatal("Unknown system command \"%s\"."
612
" Use \"%s system help\" for a list of available commands",
613
zCmd, g.argv[0]);
614
}
615

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button