1
/* stringfile.c: manage strings, files, and directories for edbrowse.
2
* Copyright (c) Karl Dahlke, 2006
3
* This file is part of the edbrowse project, released under GPL.
15
typedef struct termios termstruct;
16
#define TTY_GET_COMMAND TIOCGETA
17
#define TTY_SET_COMMAND TIOCSETA
20
typedef struct termio termstruct;
21
#define TTY_GET_COMMAND TCGETA
22
#define TTY_SET_COMMAND TCSETA
24
#include <sys/utsname.h>
29
/*********************************************************************
30
Allocate and manage memory.
31
Allocate and copy strings.
32
If we're out of memory, the program aborts. No error legs.
33
*********************************************************************/
42
errorPrint("@error allocating %u bytes", n);
47
allocZeroMem(unsigned n)
52
if(!(s = calloc(n, 1)))
53
errorPrint("@error callocating %u bytes", n);
58
reallocMem(void *p, unsigned n)
62
errorPrint("@reallocMem(p,0)");
64
errorPrint("@reallocMem(0,%d)", n);
67
if(!(s = realloc(p, n)))
68
errorPrint("@error re-allocating %u bytes", n);
75
if(s && s != EMPTYSTRING)
80
fromHex(char d, char e)
88
return ((((uchar) d) << 4) | (uchar) e);
92
appendString(char *s, const char *p)
96
s = reallocMem(s, slen + plen + 1);
102
prependString(char *s, const char *p)
104
int slen = strlen(s);
105
int plen = strlen(p);
106
char *t = allocMem(slen + plen + 1);
111
} /* prependstring */
114
skipWhite(const char **s)
117
while(isspaceByte(*t))
131
while(u > s && isspaceByte(u[-1]))
136
/* compress white space */
138
spaceCrunch(char *s, bool onespace, bool unprint)
143
for(i = j = 0; c = s[i]; ++i) {
148
s[j++] = ' ', space = true;
151
if(unprint && !isprintByte(c))
153
s[j++] = c, space = false;
156
--j; /* drop trailing space */
160
/* OO has a lot of unnecessary overhead, and a few inconveniences,
161
* but I really miss it right now. The following
162
* routines make up for the lack of simple string concatenation in C.
163
* The string space allocated is always a power of 2 - 1, starting with 1.
164
* Each of these routines puts an extra 0 on the end of the "string". */
174
stringAndString(char **s, int *l, const char *t)
177
int oldlen, newlen, x;
179
newlen = oldlen + strlen(t);
181
++newlen; /* room for the 0 */
183
if(x > oldlen) { /* must realloc */
184
newlen |= (newlen >> 1);
185
newlen |= (newlen >> 2);
186
newlen |= (newlen >> 4);
187
newlen |= (newlen >> 8);
188
newlen |= (newlen >> 16);
189
p = reallocMem(p, newlen);
192
strcpy(p + oldlen, t);
193
} /* stringAndString */
196
stringAndBytes(char **s, int *l, const char *t, int cnt)
199
int oldlen, newlen, x;
201
newlen = oldlen + cnt;
205
if(x > oldlen) { /* must realloc */
206
newlen |= (newlen >> 1);
207
newlen |= (newlen >> 2);
208
newlen |= (newlen >> 4);
209
newlen |= (newlen >> 8);
210
newlen |= (newlen >> 16);
211
p = reallocMem(p, newlen);
214
memcpy(p + oldlen, t, cnt);
216
} /* stringAndBytes */
219
stringAndChar(char **s, int *l, char c)
222
int oldlen, newlen, x;
228
if(x > oldlen) { /* must realloc */
229
newlen |= (newlen >> 1);
230
newlen |= (newlen >> 2);
231
newlen |= (newlen >> 4);
232
newlen |= (newlen >> 8);
233
newlen |= (newlen >> 16);
234
p = reallocMem(p, newlen);
239
} /* stringAndChar */
242
stringAndNum(char **s, int *l, int n)
246
stringAndString(s, l, a);
251
stringAndKnum(char **s, int *l, int n)
254
if(n && n / (1024 * 1024) * (1024 * 1024) == n)
255
sprintf(a, "%dM", n / (1024 * 1024));
256
else if(n && n / 1024 * 1024 == n)
257
sprintf(a, "%dK", n / 1024);
260
stringAndString(s, l, a);
261
} /* stringAndKnum */
264
cloneString(const char *s)
280
cloneMemory(const char *s, int n)
282
char *t = allocMem(n);
289
Cify(const char *s, int n)
292
char *t = allocMem(n + 1);
295
for(u = t; u < t + n; ++u)
302
/* pull a substring out of a larger string,
303
* and make it its own allocated string */
305
pullString(const char *s, int l)
317
pullString1(const char *s, const char *t)
319
return pullString(s, t - s);
323
stringIsNum(const char *s)
326
if(!isdigitByte(s[0]))
328
n = strtol(s, (char **)&s, 10);
335
stringIsFloat(const char *s, double *dp)
338
*dp = strtod(s, (char **)&t);
340
return false; /* extra stuff at the end */
342
} /* stringIsFloat */
345
memEqualCI(const char *s, const char *t, int len)
362
strstrCI(const char *base, const char *search)
364
int l = strlen(search);
366
if(memEqualCI(base, search, l))
374
stringEqualCI(const char *s, const char *t)
377
while((c = *s) && (d = *t)) {
391
} /* stringEqualCI */
394
stringInList(const char *const *list, const char *s)
398
errorPrint("@stringInList(null,...)");
401
if(stringEqual(s, *list))
410
stringInListCI(const char *const *list, const char *s)
414
errorPrint("@stringInListCI(null,...)");
417
if(stringEqualCI(s, *list))
423
} /* stringInListCI */
425
/* In an empty list, next and prev point back to the list, not to 0. */
426
/* We also allow zero. */
428
listIsEmpty(const struct listHead * l)
430
return l->next == l || l->next == 0;
434
initList(struct listHead *l)
436
l->prev = l->next = l;
442
struct listHead *xh = x;
443
((struct listHead *)xh->next)->prev = xh->prev;
444
((struct listHead *)xh->prev)->next = xh->next;
448
addToListFront(struct listHead *l, void *x)
450
struct listHead *xh = x;
454
((struct listHead *)xh->next)->prev = x;
455
} /* addToListFront */
458
addToListBack(struct listHead *l, void *x)
460
struct listHead *xh = x;
464
((struct listHead *)xh->prev)->next = x;
465
} /* addToListBack */
468
addAtPosition(void *p, void *x)
470
struct listHead *xh = x;
471
struct listHead *ph = p;
475
((struct listHead *)xh->next)->prev = x;
476
} /* addAtPosition */
479
freeList(struct listHead *l)
481
while(!listIsEmpty(l)) {
488
/* like isalnumByte, but allows _ and - */
494
return (c == '_' || c == '-');
500
return c == '"' || c == '\'';
503
/* Gather at most 5 parameters from a vararg list
504
* and place them in local variables.
505
* Only take as many as indicated by the percents in a sprintf string.
506
* If we blindly take more, we risk core dumps, at least on the Sun. */
509
varargLocals(va_list p, const char *msg, long *locals)
514
for(cnt = 0; cnt < 5; msg = s) {
515
s = strchr(msg, '%');
523
if(strchr("-.0123456789lhusdfxco", *s)) {
524
long n = va_arg(p, long);
527
} /* while finding percents in msg */
529
/* a little protection, in case they pass too few arguments */
531
locals[cnt++] = (long)"argmissing";
534
/* print an error message */
536
errorPrint(const char *msg, ...)
542
varargLocals(p, msg, a);
548
fprintf(stderr, "disaster, ");
549
} else if(isdigitByte(*msg)) {
551
bailflag = *msg - '0';
554
fprintf(stderr, msg, a[0], a[1], a[2], a[3], a[4]);
555
fprintf(stderr, "\n");
562
debugPrint(int lev, const char *msg, ...)
569
varargLocals(p, msg, a);
571
printf(msg, a[0], a[1], a[2], a[3], a[4]);
573
if(lev == 0 && !memcmp(msg, "warning", 7))
578
/* Show the error message, not just the question mark, after these commands. */
579
static const char showerror_cmd[] = "AbefMqrw^";
581
/* Set the error message. Type h to see the message. */
583
setError(const char *msg, ...)
594
varargLocals(p, msg, a);
596
/* Yeah I know, there's a va_list sprintf call; this is old code. */
597
sprintf(errorMsg, msg, a[0], a[1], a[2], a[3], a[4]);
600
if(strlen(errorMsg) >= sizeof (errorMsg)) {
601
printf("disaster, error message, length %d is too long\n",
611
printf("%s\n", errorMsg[0] ? errorMsg : "no errors");
615
showErrorConditional(char cmd)
617
if(helpMessagesOn || strchr(showerror_cmd, cmd))
621
} /* showErrorConditional */
626
errorPrint("1%s", errorMsg);
627
} /* showErrorAbort */
630
browseError(const char *msg, ...)
639
varargLocals(p, msg, a);
642
printf("line %d: ", browseLine);
643
cw->labels[4] = browseLine;
645
printf("browse error: ");
646
printf(msg, a[0], a[1], a[2], a[3], a[4]);
651
/* Javascript errors, we need to see these no matter what. */
653
runningError(const char *msg, ...)
660
varargLocals(p, msg, a);
663
printf("line %d: ", browseLine);
664
cw->labels[4] = browseLine;
666
printf(msg, a[0], a[1], a[2], a[3], a[4]);
671
/* Turn perl string into C string, and complain about nulls. */
681
*t = 0; /* now it's a C string */
682
return n; /* number of nulls */
685
/* The length of a perl string includes its terminating newline */
691
errorPrint("@null pointer in pstLength");
712
copyPstring(pst s, const pst t)
714
int len = pstLength(t);
719
fileIntoMemory(const char *filename, char **data, int *len)
723
char ftype = fileTypeByName(filename, false);
724
if(ftype && ftype != 'f') {
725
setError("%s is not a regular file", filename);
728
fh = open(filename, O_RDONLY | O_BINARY);
730
setError("cannot open %s", filename);
733
length = fileSizeByName(filename);
737
} /* should never hapen */
738
if(length > maxFileSize) {
739
setError("file is too large, limit 40MB");
743
buf = allocMem(length + 2);
746
n = read(fh, buf, length);
747
close(fh); /* don't need that any more */
749
setError("cannot read the contents of %s", filename);
756
} /* fileIntoMemory */
758
/* shift string to upper, lower, or mixed case */
759
/* action is u, l, or m. */
761
caseShift(char *s, char action)
778
/* mixed case left */
786
else if(mc == 1 && c == 'c')
802
/*********************************************************************
803
Manage files, directories, and terminal IO.
804
You'll see some conditional compilation when this program
805
is ported to other operating systems.
806
*********************************************************************/
808
/* Return the type of a file.
809
* Make it a capital letter if you are going through a link.
810
* I think this will work on Windows, not sure.
811
* But the link feature is Unix specific. */
814
fileTypeByName(const char *name, bool showlink)
820
if(lstat(name, &buf)) {
821
setError("cannot access %s", name);
824
mode = buf.st_mode & S_IFMT;
825
if(mode == S_IFLNK) { /* symbolic link */
827
/* If this fails, I'm guessing it's just a file. */
829
return (showlink ? 'F' : 0);
830
mode = buf.st_mode & S_IFMT;
836
/* I don't think these are Windows constructs. */
846
if(islink & showlink)
849
} /* fileTypeByName */
852
fileSizeByName(const char *name)
855
if(stat(name, &buf)) {
856
setError("cannot access %s", name);
860
} /* fileSizeByName */
863
fileTimeByName(const char *name)
866
if(stat(name, &buf)) {
867
setError("cannot access %s", name);
871
} /* fileSizeByName */
875
static termstruct savettybuf;
877
ttySaveSettings(void)
879
isInteractive = isatty(0);
881
if(ioctl(0, TTY_GET_COMMAND, &savettybuf))
882
errorPrint("@canot use ioctl() to manage the tty");
884
} /* ttySaveSettings */
887
ttyRestoreSettings(void)
890
ioctl(0, TTY_SET_COMMAND, &savettybuf);
891
} /* ttyRestoreSettings */
893
/* put the tty in raw mode.
894
* Review your Unix manual on termio.
895
* min>0 time>0: return min chars, or as many as you have received
896
* when time/10 seconds have elapsed between characters.
897
* min>0 time=0: block until min chars are received.
898
* min=0 time>0: return 1 char, or 0 if the timer expires.
899
* min=0 time=0: nonblocking, return whatever chars have been received. */
901
ttyRaw(int charcount, int timeout, bool isecho)
903
termstruct buf = savettybuf; /* structure copy */
904
buf.c_cc[VMIN] = charcount;
905
buf.c_cc[VTIME] = timeout;
906
buf.c_lflag &= ~(ICANON | ECHO);
909
ioctl(0, TTY_SET_COMMAND, &buf);
912
/* simulate MSDOS getche() system call */
920
ttyRestoreSettings();
931
ttyRestoreSettings();
938
getLetter(const char *s)
952
/* loop through the files in a directory */
953
/* Hides the differences between DOS, Unix, and NT. */
954
static bool dirstart = true;
957
nextScanFile(const char *base)
961
static char global[] = "/*.*";
965
bool allocate = false;
967
static struct _find_t dta;
969
static struct _finddata_t dta;
980
len = strlen(base) - 1;
981
p = allocMem(len + 6);
984
if(p[len] == '/' || p[len] == '\\')
990
rc = _dos_findfirst(p, (showHiddenFiles ? 077 : 073), &dta);
993
handle = _findfirst(p, &dta);
1007
("warning, the directory is inaccessible - the listing will be empty");
1015
/* read the next file */
1018
rc = _dos_findnext(&dta);
1020
rc = _findnext(handle, &dta);
1026
/* extract the base name */
1027
s = strrchr(dta.name, '/');
1028
s = s ? s + 1 : dta.name;
1029
/* weed out unwanted directories */
1030
if(stringEqual(s, "."))
1032
if(stringEqual(s, ".."))
1035
} /* end loop over files in directory */
1037
while(de = readdir(df)) {
1040
if(de->d_name[0] == '.') {
1041
if(!showHiddenFiles)
1043
if(de->d_name[1] == 0)
1045
if(de->d_name[1] == '.' && de->d_name[2] == 0)
1049
} /* end loop over files in directory */
1055
} /* nextScanFile */
1057
/* Sorted directory list. Uses textLines[]. */
1059
sortedDirList(const char *dir, int *start, int *end)
1065
*start = *end = textLinesCount;
1067
while(f = nextScanFile(dir)) {
1070
textLines[textLinesCount] = allocMem(strlen(f) + 3);
1071
strcpy((char *)textLines[textLinesCount], f);
1075
*end = textLinesCount;
1079
/* Bubble sort, the list shouldn't be too long. */
1083
for(j = *start; j < *end - 1; ++j) {
1084
if(strcmp((char *)textLines[j], (char *)textLines[j + 1]) > 0) {
1085
pst swap = textLines[j];
1086
textLines[j] = textLines[j + 1];
1087
textLines[j + 1] = swap;
1094
} /* sortedDirList */
1096
/* Expand environment variables, then wild cards.
1097
* But the result should be one and only one file.
1098
* Return the new expanded line.
1099
* Neither the original line nore the new line is allocated.
1100
* They are static char buffers that are just plain long enough. */
1103
envFile(const char *line, const char **expanded)
1105
static char line1[MAXTTYLINE];
1106
static char line2[MAXTTYLINE];
1107
const char *s, *value, *basedir;
1108
char *t, *dollar, *cut, *file;
1110
bool cc, badBrackets;
1112
char re[MAXRE + 20];
1113
const char *re_error;
1114
int re_offset, re_count, re_vector[3];
1118
/* ` supresses this stuff */
1120
*expanded = line + 1;
1124
/* quick check, nothing to do */
1125
if(!strpbrk(line, "$[*?") && line[0] != '~') {
1130
/* first the env variables */
1135
if(t >= line1 + sizeof (line1))
1138
if(c == '~' && s == line && (s[1] == '/' || s[1] == 0)) {
1139
if(strlen(home) >= sizeof (line1) - 1)
1146
if(dollar && !isalnumByte(c) && c != '_') {
1148
value = getenv(dollar + 1);
1150
setError("environement variable %s not set", dollar + 1);
1153
if(dollar + strlen(value) >= line1 + sizeof (line1) - 1)
1155
strcpy(dollar, value);
1156
t = dollar + strlen(dollar);
1159
if(c == '$' && (s[1] == '_' || isalphaByte(s[1])))
1167
/* Wildcard expansion, but somewhat limited.
1168
* The directory has to be hard coded;
1169
* we only expand stars in the filename. */
1172
for(t = line1; *t; ++t) {
1176
if(c == '[' || c == '*' || c == '?')
1179
if(!dollar) { /* nothing meta */
1183
if(cut && dollar < cut) {
1184
setError("cannot expand * ? or [] prior to the last /");
1188
/* Make sure its a directory, and build a perl regexp for the pattern. */
1191
if(cut > line1 && fileTypeByName(line1, false) != 'd') {
1192
setError("%s is not an accessible directory", line1);
1197
s = cut ? cut + 1 : line1;
1200
cc = badBrackets = false;
1202
if(t >= re + sizeof (re) - 3) {
1203
setError("shell pattern is too long");
1208
("sorry, I don't know how to expand filenames with \\ in them");
1211
/* things we need to escape */
1212
if(strchr("().+|", c))
1230
} /* loop over shell pattern */
1233
if(badBrackets | cc) {
1234
setError("improperly formed [] pattern");
1238
debugPrint(7, "shell regexp %s", re);
1239
re_cc = pcre_compile(re, 0, &re_error, &re_offset, 0);
1241
setError("error compiling the shell pattern, %s", re_error);
1246
cc = false; /* long flag */
1254
while(file = nextScanFile(basedir)) {
1257
re_count = pcre_exec(re_cc, 0, file, strlen(file), 0, 0, re_vector, 3);
1260
setError("unexpected error while evaluating the shell pattern");
1268
if((cut ? strlen(line1) : 0) + strlen(file) >= sizeof (line2) - 2)
1271
sprintf(line2, "%s/%s", line1, file);
1273
strcpy(line2, file);
1276
if(filecount != 1) {
1277
setError(filecount ? "shell pattern matches more than one file" :
1278
"shell pattern does not match any files");
1288
setError("line becomes too long when shell variables are expanded");
1292
static struct utsname utsbuf;
1298
return utsbuf.sysname;
1302
currentMachine(void)
1305
return utsbuf.machine;
1306
} /* currentMachine */