blob: 2b32109902e52023f8e341ca267e9a0a7fd1989e [file] [log] [blame]
landleycd9dfc32006-10-18 18:38:16 -04001/* vi: set sw=4 ts=4 :*/
Charlie Shepherd54524c92008-01-25 12:36:24 +00002/* lib.c - reusable stuff.
landley4f344e32006-10-05 16:18:03 -04003 *
landleycd9dfc32006-10-18 18:38:16 -04004 * Functions with the x prefix are wrappers for library functions. They either
5 * succeed or kill the program with an error message, but never return failure.
6 * They usually have the same arguments and return value as the function they
7 * wrap.
landley09ea7ac2006-10-30 01:38:00 -05008 *
9 * Copyright 2006 Rob Landley <rob@landley.net>
landley4f344e32006-10-05 16:18:03 -040010 */
11
12#include "toys.h"
13
Rob Landleye15850a2007-11-19 01:51:00 -060014// Strcpy with size checking: exit if there's not enough space for the string.
15void xstrcpy(char *dest, char *src, size_t size)
Rob Landley18d43ff2007-06-07 15:19:44 -040016{
Rob Landleye15850a2007-11-19 01:51:00 -060017 if (strlen(src)+1 > size) error_exit("xstrcpy");
18 strcpy(dest, src);
Rob Landley18d43ff2007-06-07 15:19:44 -040019}
Rob Landley18d43ff2007-06-07 15:19:44 -040020
landley09ea7ac2006-10-30 01:38:00 -050021void verror_msg(char *msg, int err, va_list va)
22{
Rob Landley12138e42008-01-27 15:26:08 -060023 char *s = ": %s";
24
landley09ea7ac2006-10-30 01:38:00 -050025 fprintf(stderr, "%s: ", toys.which->name);
Rob Landley12138e42008-01-27 15:26:08 -060026 if (msg) vfprintf(stderr, msg, va);
27 else s+=2;
28 if (err) fprintf(stderr, s, strerror(err));
landley09ea7ac2006-10-30 01:38:00 -050029 putc('\n', stderr);
30}
31
32void error_msg(char *msg, ...)
33{
34 va_list va;
35
36 va_start(va, msg);
37 verror_msg(msg, 0, va);
38 va_end(va);
39}
40
41void perror_msg(char *msg, ...)
42{
43 va_list va;
44
45 va_start(va, msg);
46 verror_msg(msg, errno, va);
47 va_end(va);
48}
49
landley4f344e32006-10-05 16:18:03 -040050// Die with an error message.
51void error_exit(char *msg, ...)
52{
landley09ea7ac2006-10-30 01:38:00 -050053 va_list va;
landley4f344e32006-10-05 16:18:03 -040054
Rob Landleyd06c58d2007-10-11 15:36:36 -050055 if (CFG_HELP && toys.exithelp) {
56 *toys.optargs=*toys.argv;
Rob Landley55928b12008-01-19 17:43:27 -060057 USE_HELP(help_main();) // dear gcc: shut up.
Rob Landleyd06c58d2007-10-11 15:36:36 -050058 fprintf(stderr,"\n");
59 }
60
landley09ea7ac2006-10-30 01:38:00 -050061 va_start(va, msg);
62 verror_msg(msg, 0, va);
63 va_end(va);
64
Rob Landleyaaffc072007-12-09 15:35:42 -060065 exit(!toys.exitval ? 1 : toys.exitval);
landley09ea7ac2006-10-30 01:38:00 -050066}
67
Rob Landley055cfcb2007-01-14 20:20:06 -050068
landley09ea7ac2006-10-30 01:38:00 -050069// Die with an error message and strerror(errno)
70void perror_exit(char *msg, ...)
71{
72 va_list va;
73
74 va_start(va, msg);
75 verror_msg(msg, errno, va);
76 va_end(va);
77
Rob Landleyaaffc072007-12-09 15:35:42 -060078 exit(!toys.exitval ? 1 : toys.exitval);
landley4f344e32006-10-05 16:18:03 -040079}
80
landley4f344e32006-10-05 16:18:03 -040081// Die unless we can allocate memory.
82void *xmalloc(size_t size)
83{
84 void *ret = malloc(size);
85 if (!ret) error_exit("xmalloc");
landleycd9dfc32006-10-18 18:38:16 -040086
87 return ret;
landley4f344e32006-10-05 16:18:03 -040088}
89
landleycd9dfc32006-10-18 18:38:16 -040090// Die unless we can allocate prezeroed memory.
91void *xzalloc(size_t size)
92{
93 void *ret = xmalloc(size);
Rob Landley2b54b1a2012-02-18 22:44:11 -060094 memset(ret, 0, size);
landleycd9dfc32006-10-18 18:38:16 -040095 return ret;
96}
97
98// Die unless we can change the size of an existing allocation, possibly
99// moving it. (Notice different arguments from libc function.)
Rob Landley0c93f6c2007-04-29 19:55:21 -0400100void *xrealloc(void *ptr, size_t size)
landleycd9dfc32006-10-18 18:38:16 -0400101{
Rob Landley0c93f6c2007-04-29 19:55:21 -0400102 ptr = realloc(ptr, size);
103 if (!ptr) error_exit("xrealloc");
104
105 return ptr;
landleycd9dfc32006-10-18 18:38:16 -0400106}
107
Rob Landleyfa98d012006-11-02 02:57:27 -0500108// Die unless we can allocate a copy of this many bytes of string.
Rob Landley1e01cd12010-01-05 10:48:32 -0600109char *xstrndup(char *s, size_t n)
landley4f344e32006-10-05 16:18:03 -0400110{
Rob Landley3fc4e0f2008-04-13 00:29:00 -0500111 char *ret = xmalloc(++n);
112 strncpy(ret, s, n);
113 ret[--n]=0;
Rob Landley2c226852007-11-15 18:30:30 -0600114
landley4f344e32006-10-05 16:18:03 -0400115 return ret;
116}
117
Rob Landleyfa98d012006-11-02 02:57:27 -0500118// Die unless we can allocate a copy of this string.
Rob Landley1e01cd12010-01-05 10:48:32 -0600119char *xstrdup(char *s)
Rob Landleyfa98d012006-11-02 02:57:27 -0500120{
Rob Landleyf6418542008-01-27 16:22:41 -0600121 return xstrndup(s, strlen(s));
Rob Landleyfa98d012006-11-02 02:57:27 -0500122}
123
landley00f87f12006-10-25 18:38:37 -0400124// Die unless we can allocate enough space to sprintf() into.
125char *xmsprintf(char *format, ...)
126{
Rob Landley0d8dfb22007-06-15 15:16:46 -0400127 va_list va, va2;
landley00f87f12006-10-25 18:38:37 -0400128 int len;
129 char *ret;
Rob Landley2c226852007-11-15 18:30:30 -0600130
landley00f87f12006-10-25 18:38:37 -0400131 va_start(va, format);
Rob Landley0d8dfb22007-06-15 15:16:46 -0400132 va_copy(va2, va);
133
134 // How long is it?
landley00f87f12006-10-25 18:38:37 -0400135 len = vsnprintf(0, 0, format, va);
136 len++;
137 va_end(va);
138
139 // Allocate and do the sprintf()
140 ret = xmalloc(len);
Rob Landley2c226852007-11-15 18:30:30 -0600141 vsnprintf(ret, len, format, va2);
Rob Landley0d8dfb22007-06-15 15:16:46 -0400142 va_end(va2);
landley00f87f12006-10-25 18:38:37 -0400143
144 return ret;
145}
146
Rob Landley24d1d452007-01-20 18:04:20 -0500147void xprintf(char *format, ...)
148{
149 va_list va;
150 va_start(va, format);
151
152 vprintf(format, va);
153 if (ferror(stdout)) perror_exit("write");
154}
155
Rob Landley5084fea2007-06-18 00:14:03 -0400156void xputs(char *s)
157{
158 if (EOF == puts(s)) perror_exit("write");
159}
160
Rob Landley24d1d452007-01-20 18:04:20 -0500161void xputc(char c)
162{
163 if (EOF == fputc(c, stdout)) perror_exit("write");
164}
165
166void xflush(void)
167{
168 if (fflush(stdout)) perror_exit("write");;
169}
170
landleycd9dfc32006-10-18 18:38:16 -0400171// Die unless we can exec argv[] (or run builtin command). Note that anything
172// with a path isn't a builtin, so /bin/sh won't match the builtin sh.
landley09ea7ac2006-10-30 01:38:00 -0500173void xexec(char **argv)
landley4f344e32006-10-05 16:18:03 -0400174{
landleycd9dfc32006-10-18 18:38:16 -0400175 toy_exec(argv);
landley4f344e32006-10-05 16:18:03 -0400176 execvp(argv[0], argv);
Rob Landley26e7b5e2012-02-02 07:27:35 -0600177
178 perror_exit("exec %s", argv[0]);
landley4f344e32006-10-05 16:18:03 -0400179}
180
Rob Landleyd3e9d642007-01-08 03:25:47 -0500181void xaccess(char *path, int flags)
182{
Charlie Shepherd94dd3e72008-01-25 12:54:31 +0000183 if (access(path, flags)) perror_exit("Can't access '%s'", path);
Rob Landleyd3e9d642007-01-08 03:25:47 -0500184}
185
Rob Landleye745d8e2007-12-20 06:30:19 -0600186// Die unless we can delete a file. (File must exist to be deleted.)
187void xunlink(char *path)
188{
189 if (unlink(path)) perror_exit("unlink '%s'", path);
190}
191
landley4f344e32006-10-05 16:18:03 -0400192// Die unless we can open/create a file, returning file descriptor.
Rob Landley1322beb2007-01-07 22:51:12 -0500193int xcreate(char *path, int flags, int mode)
landley4f344e32006-10-05 16:18:03 -0400194{
195 int fd = open(path, flags, mode);
Rob Landley961e1712007-11-04 15:31:06 -0600196 if (fd == -1) perror_exit("%s", path);
landley4f344e32006-10-05 16:18:03 -0400197 return fd;
198}
199
Rob Landley1322beb2007-01-07 22:51:12 -0500200// Die unless we can open a file, returning file descriptor.
201int xopen(char *path, int flags)
202{
203 return xcreate(path, flags, 0);
204}
205
Rob Landleybc078652007-12-15 21:47:25 -0600206void xclose(int fd)
207{
208 if (close(fd)) perror_exit("xclose");
209}
210
Rob Landleyeb7ea222012-04-14 22:30:41 -0500211int xdup(int fd)
212{
Rob Landley98356222012-06-09 19:49:37 -0500213 if (fd != -1) {
214 fd = dup(fd);
215 if (fd == -1) perror_exit("xdup");
216 }
Rob Landleyeb7ea222012-04-14 22:30:41 -0500217 return fd;
218}
219
landley4f344e32006-10-05 16:18:03 -0400220// Die unless we can open/create a file, returning FILE *.
221FILE *xfopen(char *path, char *mode)
222{
223 FILE *f = fopen(path, mode);
Charlie Shepherd94dd3e72008-01-25 12:54:31 +0000224 if (!f) perror_exit("No file %s", path);
landley4f344e32006-10-05 16:18:03 -0400225 return f;
226}
landley00f87f12006-10-25 18:38:37 -0400227
landley64b2e232006-10-30 10:01:19 -0500228// Keep reading until full or EOF
Rob Landley90163772007-01-18 21:54:08 -0500229ssize_t readall(int fd, void *buf, size_t len)
landley64b2e232006-10-30 10:01:19 -0500230{
Rob Landley90163772007-01-18 21:54:08 -0500231 size_t count = 0;
Rob Landley15b23152008-07-18 04:15:59 -0500232
Rob Landley90163772007-01-18 21:54:08 -0500233 while (count<len) {
234 int i = read(fd, buf+count, len-count);
Rob Landley15b23152008-07-18 04:15:59 -0500235 if (!i) break;
landley64b2e232006-10-30 10:01:19 -0500236 if (i<0) return i;
237 count += i;
238 }
239
240 return count;
241}
242
Rob Landleyf3e452a2007-01-08 02:49:39 -0500243// Keep writing until done or EOF
Rob Landley90163772007-01-18 21:54:08 -0500244ssize_t writeall(int fd, void *buf, size_t len)
Rob Landleyf3e452a2007-01-08 02:49:39 -0500245{
Rob Landley90163772007-01-18 21:54:08 -0500246 size_t count = 0;
247 while (count<len) {
248 int i = write(fd, buf+count, len-count);
249 if (i<1) return i;
Rob Landleyf3e452a2007-01-08 02:49:39 -0500250 count += i;
251 }
252
253 return count;
254}
255
Rob Landley055cfcb2007-01-14 20:20:06 -0500256// Die if there's an error other than EOF.
Rob Landley90163772007-01-18 21:54:08 -0500257size_t xread(int fd, void *buf, size_t len)
landley64b2e232006-10-30 10:01:19 -0500258{
Rob Landleyf265d042011-12-28 13:01:12 -0600259 ssize_t ret = read(fd, buf, len);
260 if (ret < 0) perror_exit("xread");
Rob Landley055cfcb2007-01-14 20:20:06 -0500261
Rob Landleyf265d042011-12-28 13:01:12 -0600262 return ret;
Rob Landley055cfcb2007-01-14 20:20:06 -0500263}
264
Rob Landley90163772007-01-18 21:54:08 -0500265void xreadall(int fd, void *buf, size_t len)
Rob Landley055cfcb2007-01-14 20:20:06 -0500266{
Rob Landley90163772007-01-18 21:54:08 -0500267 if (len != readall(fd, buf, len)) perror_exit("xreadall");
Rob Landley055cfcb2007-01-14 20:20:06 -0500268}
landley00f87f12006-10-25 18:38:37 -0400269
Rob Landley90163772007-01-18 21:54:08 -0500270// There's no xwriteall(), just xwrite(). When we read, there may or may not
271// be more data waiting. When we write, there is data and it had better go
272// somewhere.
273
274void xwrite(int fd, void *buf, size_t len)
Rob Landleyf3e452a2007-01-08 02:49:39 -0500275{
Rob Landley90163772007-01-18 21:54:08 -0500276 if (len != writeall(fd, buf, len)) perror_exit("xwrite");
Rob Landleyf3e452a2007-01-08 02:49:39 -0500277}
278
Rob Landley52476712009-01-18 16:19:25 -0600279// Die if lseek fails, probably due to being called on a pipe.
280
281off_t xlseek(int fd, off_t offset, int whence)
282{
283 offset = lseek(fd, offset, whence);
284 if (offset<0) perror_exit("lseek");
285
286 return offset;
287}
288
Rob Landley2037b832012-07-15 16:56:20 -0500289off_t lskip(int fd, off_t offset)
290{
291 off_t and = lseek(fd, offset, SEEK_CUR);
292
293 if (and != -1 && offset >= lseek(fd, offset, SEEK_END)
294 && offset+and == lseek(fd, offset+and, SEEK_SET)) return 0;
295 else {
296 char buf[4096];
297 while (offset>0) {
298 int try = offset>sizeof(buf) ? sizeof(buf) : offset, or;
299
300 or = readall(fd, buf, try);
301 if (or < 0) perror_msg("lskip to %lld", (long long)offset);
302 else offset -= try;
303 if (or < try) break;
304 }
305
306 return offset;
307 }
308}
309
landley00f87f12006-10-25 18:38:37 -0400310char *xgetcwd(void)
311{
312 char *buf = getcwd(NULL, 0);
Rob Landley24d1d452007-01-20 18:04:20 -0500313 if (!buf) perror_exit("xgetcwd");
landley09ea7ac2006-10-30 01:38:00 -0500314
315 return buf;
landley00f87f12006-10-25 18:38:37 -0400316}
317
Rob Landleyd25f7e42007-02-03 14:11:26 -0500318void xstat(char *path, struct stat *st)
319{
Rob Landleyf6418542008-01-27 16:22:41 -0600320 if(stat(path, st)) perror_exit("Can't stat %s", path);
Rob Landleyd25f7e42007-02-03 14:11:26 -0500321}
322
Rob Landleyfa98d012006-11-02 02:57:27 -0500323// Cannonicalizes path by removing ".", "..", and "//" elements. This is not
Rob Landleyc6f481c2006-12-30 22:01:47 -0500324// the same as realpath(), where "dir/.." could wind up somewhere else by
325// following symlinks.
Rob Landleyfa98d012006-11-02 02:57:27 -0500326char *xabspath(char *path)
landley00f87f12006-10-25 18:38:37 -0400327{
Rob Landleyfa98d012006-11-02 02:57:27 -0500328 char *from, *to;
landley00f87f12006-10-25 18:38:37 -0400329
Rob Landleyfa98d012006-11-02 02:57:27 -0500330 // If this isn't an absolute path, make it one with cwd.
331 if (path[0]!='/') {
332 char *cwd=xgetcwd();
Rob Landleyf6418542008-01-27 16:22:41 -0600333 path = xmsprintf("%s/%s", cwd, path);
Rob Landleyfa98d012006-11-02 02:57:27 -0500334 free(cwd);
335 } else path = xstrdup(path);
landley00f87f12006-10-25 18:38:37 -0400336
Rob Landleyfa98d012006-11-02 02:57:27 -0500337 // Loop through path elements
338 from = to = path;
339 while (*from) {
340
341 // Continue any current path component.
342 if (*from!='/') {
343 *(to++) = *(from++);
344 continue;
landley00f87f12006-10-25 18:38:37 -0400345 }
Rob Landleyfa98d012006-11-02 02:57:27 -0500346
347 // Skip duplicate slashes.
348 while (*from=='/') from++;
Rob Landley2c226852007-11-15 18:30:30 -0600349
Rob Landleyfa98d012006-11-02 02:57:27 -0500350 // Start of a new filename. Handle . and ..
351 while (*from=='.') {
352 // Skip .
353 if (from[1]=='/') from += 2;
354 else if (!from[1]) from++;
355 // Back up for ..
356 else if (from[1]=='.') {
357 if (from[2]=='/') from +=3;
358 else if(!from[2]) from+=2;
359 else break;
360 while (to>path && *(--to)!='/');
361 } else break;
362 }
363 // Add directory separator slash.
364 *(to++) = '/';
365 }
366 *to = 0;
367
368 return path;
369}
370
Rob Landleyeec46372012-06-01 13:50:41 -0500371// Resolve all symlinks, returning malloc() memory.
372char *xrealpath(char *path)
373{
374 char *new = realpath(path, NULL);
375 if (!new) perror_exit("realpath '%s'", path);
376 return new;
377}
378
Rob Landley988abb32008-05-12 00:52:27 -0500379void xchdir(char *path)
380{
Rob Landleyddae5e92009-01-25 16:34:46 -0600381 if (chdir(path)) error_exit("chdir '%s'", path);
Rob Landley988abb32008-05-12 00:52:27 -0500382}
383
Rob Landley35483412007-12-27 21:36:33 -0600384// Ensure entire path exists.
385// If mode != -1 set permissions on newly created dirs.
386// Requires that path string be writable (for temporary null terminators).
387void xmkpath(char *path, int mode)
388{
389 char *p, old;
390 mode_t mask;
391 int rc;
392 struct stat st;
393
394 for (p = path; ; p++) {
395 if (!*p || *p == '/') {
396 old = *p;
397 *p = rc = 0;
398 if (stat(path, &st) || !S_ISDIR(st.st_mode)) {
399 if (mode != -1) {
400 mask=umask(0);
401 rc = mkdir(path, mode);
402 umask(mask);
403 } else rc = mkdir(path, 0777);
404 }
405 *p = old;
Rob Landleyf6418542008-01-27 16:22:41 -0600406 if(rc) perror_exit("mkpath '%s'", path);
Rob Landley35483412007-12-27 21:36:33 -0600407 }
408 if (!*p) break;
409 }
410}
Rob Landleye0377fb2010-01-05 12:17:05 -0600411
412// setuid() can fail (for example, too many processes belonging to that user),
413// which opens a security hole if the process continues as the original user.
414
415void xsetuid(uid_t uid)
416{
417 if (setuid(uid)) perror_exit("xsetuid");
418}
419
420
Rob Landley0a04b3e2006-11-03 00:05:52 -0500421// Find all file in a colon-separated path with access type "type" (generally
422// X_OK or R_OK). Returns a list of absolute paths to each file found, in
423// order.
424
425struct string_list *find_in_path(char *path, char *filename)
Rob Landleyfa98d012006-11-02 02:57:27 -0500426{
Rob Landley59f490c2008-05-17 17:52:51 -0500427 struct string_list *rlist = NULL, **prlist=&rlist;
Rob Landley0a04b3e2006-11-03 00:05:52 -0500428 char *cwd = xgetcwd();
Rob Landleyfa98d012006-11-02 02:57:27 -0500429
430 for (;;) {
Rob Landleyb7529b62012-03-08 20:14:55 -0600431 char *next = path ? strchr(path, ':') : NULL;
Rob Landleyfa98d012006-11-02 02:57:27 -0500432 int len = next ? next-path : strlen(path);
Rob Landley0a04b3e2006-11-03 00:05:52 -0500433 struct string_list *rnext;
434 struct stat st;
Rob Landleyfa98d012006-11-02 02:57:27 -0500435
Rob Landley0a04b3e2006-11-03 00:05:52 -0500436 rnext = xmalloc(sizeof(void *) + strlen(filename)
437 + (len ? len : strlen(cwd)) + 2);
438 if (!len) sprintf(rnext->str, "%s/%s", cwd, filename);
Rob Landleyfa98d012006-11-02 02:57:27 -0500439 else {
Rob Landley0a04b3e2006-11-03 00:05:52 -0500440 char *res = rnext->str;
Rob Landleyfa98d012006-11-02 02:57:27 -0500441 strncpy(res, path, len);
Rob Landley0a04b3e2006-11-03 00:05:52 -0500442 res += len;
443 *(res++) = '/';
444 strcpy(res, filename);
Rob Landleyfa98d012006-11-02 02:57:27 -0500445 }
446
Rob Landley0a04b3e2006-11-03 00:05:52 -0500447 // Confirm it's not a directory.
448 if (!stat(rnext->str, &st) && S_ISREG(st.st_mode)) {
Rob Landley59f490c2008-05-17 17:52:51 -0500449 *prlist = rnext;
450 rnext->next = NULL;
451 prlist = &(rnext->next);
Rob Landley0a04b3e2006-11-03 00:05:52 -0500452 } else free(rnext);
Rob Landleyfa98d012006-11-02 02:57:27 -0500453
Rob Landleyfa98d012006-11-02 02:57:27 -0500454 if (!next) break;
455 path += len;
456 path++;
landley00f87f12006-10-25 18:38:37 -0400457 }
458 free(cwd);
459
Rob Landley0a04b3e2006-11-03 00:05:52 -0500460 return rlist;
landley00f87f12006-10-25 18:38:37 -0400461}
landley09ea7ac2006-10-30 01:38:00 -0500462
463// Convert unsigned int to ascii, writing into supplied buffer. A truncated
464// result contains the first few digits of the result ala strncpy, and is
465// always null terminated (unless buflen is 0).
466void utoa_to_buf(unsigned n, char *buf, unsigned buflen)
467{
468 int i, out = 0;
469
470 if (buflen) {
471 for (i=1000000000; i; i/=10) {
472 int res = n/i;
473
474 if ((res || out || i == 1) && --buflen>0) {
475 out++;
476 n -= res*i;
477 *buf++ = '0' + res;
478 }
479 }
480 *buf = 0;
481 }
482}
483
484// Convert signed integer to ascii, using utoa_to_buf()
485void itoa_to_buf(int n, char *buf, unsigned buflen)
486{
487 if (buflen && n<0) {
488 n = -n;
489 *buf++ = '-';
490 buflen--;
491 }
492 utoa_to_buf((unsigned)n, buf, buflen);
493}
494
495// This static buffer is used by both utoa() and itoa(), calling either one a
496// second time will overwrite the previous results.
497//
498// The longest 32 bit integer is -2 billion plus a null terminator: 12 bytes.
499// Note that int is always 32 bits on any remotely unix-like system, see
500// http://www.unix.org/whitepapers/64bit.html for details.
501
502static char itoa_buf[12];
503
504// Convert unsigned integer to ascii, returning a static buffer.
505char *utoa(unsigned n)
506{
507 utoa_to_buf(n, itoa_buf, sizeof(itoa_buf));
508
509 return itoa_buf;
510}
511
512char *itoa(int n)
513{
514 itoa_to_buf(n, itoa_buf, sizeof(itoa_buf));
515
516 return itoa_buf;
517}
Rob Landley055cfcb2007-01-14 20:20:06 -0500518
Rob Landleyf5757162007-02-16 21:08:22 -0500519// atol() with the kilo/mega/giga/tera/peta/exa extensions.
520// (zetta and yotta don't fit in 64 bits.)
Rob Landley5e6dca62012-02-09 06:09:27 -0600521long atolx(char *numstr)
Rob Landleyf5757162007-02-16 21:08:22 -0500522{
Rob Landleyb8ef8892012-06-30 16:31:37 -0500523 char *c, *suffixes="bkmgtpe", *end;
Rob Landley5e6dca62012-02-09 06:09:27 -0600524 long val = strtol(numstr, &c, 0);
Rob Landleyf5757162007-02-16 21:08:22 -0500525
Rob Landley6a6dee32007-11-04 15:32:59 -0600526 if (*c) {
Rob Landley2037b832012-07-15 16:56:20 -0500527 if (c != numstr && (end = strchr(suffixes, tolower(*c)))) {
Rob Landleyb8ef8892012-06-30 16:31:37 -0500528 int shift = end-suffixes;
529 if (shift--) val *= 1024L<<(shift*10);
530 } else {
Rob Landley5e6dca62012-02-09 06:09:27 -0600531 while (isspace(*c)) c++;
532 if (*c) error_exit("not integer: %s", numstr);
533 }
Rob Landley6a6dee32007-11-04 15:32:59 -0600534 }
Rob Landleyad63f4b2011-12-12 15:19:52 -0600535
Rob Landleyf5757162007-02-16 21:08:22 -0500536 return val;
537}
538
Rob Landleyeb7ea222012-04-14 22:30:41 -0500539int numlen(long l)
540{
541 int len = 0;
542 while (l) {
543 l /= 10;
544 len++;
545 }
546 return len;
547}
548
Rob Landley2037b832012-07-15 16:56:20 -0500549int stridx(char *haystack, char needle)
550{
551 char *off;
552
553 if (!needle) return -1;
554 off = strchr(haystack, needle);
555 if (!off) return -1;
556
557 return off-haystack;
558}
559
Rob Landley055cfcb2007-01-14 20:20:06 -0500560// Return how long the file at fd is, if there's any way to determine it.
561off_t fdlength(int fd)
562{
Rob Landleyf15387d2008-07-18 05:43:44 -0500563 off_t bottom = 0, top = 0, pos, old;
Rob Landleye2580db2007-01-23 13:20:38 -0500564 int size;
Rob Landley055cfcb2007-01-14 20:20:06 -0500565
566 // If the ioctl works for this, return it.
567
Rob Landleye2580db2007-01-23 13:20:38 -0500568 if (ioctl(fd, BLKGETSIZE, &size) >= 0) return size*512L;
Rob Landley055cfcb2007-01-14 20:20:06 -0500569
570 // If not, do a binary search for the last location we can read. (Some
571 // block devices don't do BLKGETSIZE right.) This should probably have
572 // a CONFIG option...
573
Rob Landleyf15387d2008-07-18 05:43:44 -0500574 old = lseek(fd, 0, SEEK_CUR);
Rob Landley055cfcb2007-01-14 20:20:06 -0500575 do {
576 char temp;
577
578 pos = bottom + (top - bottom) / 2;
579
580 // If we can read from the current location, it's bigger.
581
Rob Landleyb3a33822007-01-25 16:10:37 -0500582 if (lseek(fd, pos, 0)>=0 && read(fd, &temp, 1)==1) {
Rob Landley055cfcb2007-01-14 20:20:06 -0500583 if (bottom == top) bottom = top = (top+1) * 2;
584 else bottom = pos;
585
586 // If we can't, it's smaller.
587
588 } else {
589 if (bottom == top) {
590 if (!top) return 0;
591 bottom = top/2;
592 } else top = pos;
593 }
594 } while (bottom + 1 != top);
595
Rob Landleyf15387d2008-07-18 05:43:44 -0500596 lseek(fd, old, SEEK_SET);
597
Rob Landley055cfcb2007-01-14 20:20:06 -0500598 return pos + 1;
599}
600
Rob Landley0c93f6c2007-04-29 19:55:21 -0400601// This can return null (meaning file not found). It just won't return null
602// for memory allocation reasons.
603char *xreadlink(char *name)
604{
605 int len, size = 0;
606 char *buf = 0;
607
608 // Grow by 64 byte chunks until it's big enough.
609 for(;;) {
610 size +=64;
611 buf = xrealloc(buf, size);
612 len = readlink(name, buf, size);
613
614 if (len<0) {
615 free(buf);
616 return 0;
617 }
618 if (len<size) {
619 buf[len]=0;
620 return buf;
621 }
622 }
623}
624
Rob Landleyb3a33822007-01-25 16:10:37 -0500625/*
626 This might be of use or might not. Unknown yet...
627
Rob Landley055cfcb2007-01-14 20:20:06 -0500628// Read contents of file as a single freshly allocated nul-terminated string.
629char *readfile(char *name)
630{
631 off_t len;
632 int fd;
633 char *buf;
634
Rob Landley2c226852007-11-15 18:30:30 -0600635 fd = open(name, O_RDONLY);
Rob Landley055cfcb2007-01-14 20:20:06 -0500636 if (fd == -1) return 0;
637 len = fdlength(fd);
638 buf = xmalloc(len+1);
Rob Landley8d43d912011-11-13 21:05:28 -0600639 buf[readall(fd, buf, len)] = 0;
Rob Landley055cfcb2007-01-14 20:20:06 -0500640
641 return buf;
642}
643
644char *xreadfile(char *name)
645{
646 char *buf = readfile(name);
Rob Landley24d1d452007-01-20 18:04:20 -0500647 if (!buf) perror_exit("xreadfile %s", name);
Rob Landley055cfcb2007-01-14 20:20:06 -0500648 return buf;
649}
650
651*/
652
653// Open a /var/run/NAME.pid file, dying if we can't write it or if it currently
654// exists and is this executable.
655void xpidfile(char *name)
656{
657 char pidfile[256], spid[32];
658 int i, fd;
659 pid_t pid;
660
661 sprintf(pidfile, "/var/run/%s.pid", name);
662 // Try three times to open the sucker.
663 for (i=0; i<3; i++) {
664 fd = open(pidfile, O_CREAT|O_EXCL, 0644);
665 if (fd != -1) break;
666
667 // If it already existed, read it. Loop for race condition.
668 fd = open(pidfile, O_RDONLY);
669 if (fd == -1) continue;
670
671 // Is the old program still there?
672 spid[xread(fd, spid, sizeof(spid)-1)] = 0;
673 close(fd);
674 pid = atoi(spid);
675 if (fd < 1 || kill(pid, 0) == ESRCH) unlink(pidfile);
676
677 // An else with more sanity checking might be nice here.
678 }
Rob Landley2c226852007-11-15 18:30:30 -0600679
Rob Landley055cfcb2007-01-14 20:20:06 -0500680 if (i == 3) error_exit("xpidfile %s", name);
681
682 xwrite(fd, spid, sprintf(spid, "%ld\n", (long)getpid()));
683 close(fd);
684}
Rob Landley7634b552007-11-29 17:49:50 -0600685
Rob Landley2bfaaf22008-07-03 19:19:00 -0500686// Iterate through an array of files, opening each one and calling a function
687// on that filehandle and name. The special filename "-" means stdin if
688// flags is O_RDONLY, stdout otherwise. An empty argument list calls
689// function() on just stdin/stdout.
690//
691// Note: read only filehandles are automatically closed when function()
692// returns, but writeable filehandles must be close by function()
Rob Landleyad63f4b2011-12-12 15:19:52 -0600693void loopfiles_rw(char **argv, int flags, int permissions, int failok,
694 void (*function)(int fd, char *name))
Rob Landley7634b552007-11-29 17:49:50 -0600695{
696 int fd;
697
698 // If no arguments, read from stdin.
Rob Landley2bfaaf22008-07-03 19:19:00 -0500699 if (!*argv) function(flags ? 1 : 0, "-");
Rob Landley7634b552007-11-29 17:49:50 -0600700 else do {
701 // Filename "-" means read from stdin.
702 // Inability to open a file prints a warning, but doesn't exit.
703
704 if (!strcmp(*argv,"-")) fd=0;
Rob Landleyad63f4b2011-12-12 15:19:52 -0600705 else if (0>(fd = open(*argv, flags, permissions)) && !failok) {
Rob Landleyf6418542008-01-27 16:22:41 -0600706 perror_msg("%s", *argv);
Rob Landley7634b552007-11-29 17:49:50 -0600707 toys.exitval = 1;
Rob Landley3632d5d2008-01-01 02:39:29 -0600708 continue;
Rob Landley7634b552007-11-29 17:49:50 -0600709 }
710 function(fd, *argv);
Rob Landley2bfaaf22008-07-03 19:19:00 -0500711 if (!flags) close(fd);
Rob Landley7634b552007-11-29 17:49:50 -0600712 } while (*++argv);
713}
Rob Landleybc078652007-12-15 21:47:25 -0600714
Rob Landleyad63f4b2011-12-12 15:19:52 -0600715// Call loopfiles_rw with O_RDONLY and !failok (common case).
Rob Landley2bfaaf22008-07-03 19:19:00 -0500716void loopfiles(char **argv, void (*function)(int fd, char *name))
717{
Rob Landleyad63f4b2011-12-12 15:19:52 -0600718 loopfiles_rw(argv, O_RDONLY, 0, 0, function);
Rob Landley2bfaaf22008-07-03 19:19:00 -0500719}
720
Rob Landleybc078652007-12-15 21:47:25 -0600721// Slow, but small.
722
Rob Landley3fc4e0f2008-04-13 00:29:00 -0500723char *get_rawline(int fd, long *plen, char end)
Rob Landleybc078652007-12-15 21:47:25 -0600724{
725 char c, *buf = NULL;
726 long len = 0;
727
728 for (;;) {
729 if (1>read(fd, &c, 1)) break;
Rob Landleyd6b26132009-04-16 17:03:38 -0500730 if (!(len & 63)) buf=xrealloc(buf, len+65);
Rob Landley3fc4e0f2008-04-13 00:29:00 -0500731 if ((buf[len++]=c) == end) break;
Rob Landleybc078652007-12-15 21:47:25 -0600732 }
733 if (buf) buf[len]=0;
734 if (plen) *plen = len;
735
736 return buf;
737}
738
739char *get_line(int fd)
740{
741 long len;
Rob Landley3fc4e0f2008-04-13 00:29:00 -0500742 char *buf = get_rawline(fd, &len, '\n');
Rob Landleybc078652007-12-15 21:47:25 -0600743
744 if (buf && buf[--len]=='\n') buf[len]=0;
745
746 return buf;
747}
748
749// Copy the rest of in to out and close both files.
750
751void xsendfile(int in, int out)
752{
753 long len;
Rob Landley42ecbab2007-12-18 02:02:21 -0600754 char buf[4096];
Rob Landleybc078652007-12-15 21:47:25 -0600755
756 if (in<0) return;
757 for (;;) {
Rob Landley42ecbab2007-12-18 02:02:21 -0600758 len = xread(in, buf, 4096);
Rob Landleybc078652007-12-15 21:47:25 -0600759 if (len<1) break;
Rob Landley42ecbab2007-12-18 02:02:21 -0600760 xwrite(out, buf, len);
Rob Landleybc078652007-12-15 21:47:25 -0600761 }
Rob Landley42ecbab2007-12-18 02:02:21 -0600762}
763
Rob Landley67a069d2012-06-03 00:32:12 -0500764int wfchmodat(int fd, char *name, mode_t mode)
765{
766 int rc = fchmodat(fd, name, mode, 0);
767
768 if (rc) {
769 perror_msg("chmod '%s' to %04o", name, mode);
770 toys.exitval=1;
771 }
772 return rc;
773}
774
Rob Landley42ecbab2007-12-18 02:02:21 -0600775// Open a temporary file to copy an existing file into.
776int copy_tempfile(int fdin, char *name, char **tempname)
777{
778 struct stat statbuf;
779 int fd;
780
781 *tempname = xstrndup(name, strlen(name)+6);
782 strcat(*tempname,"XXXXXX");
783 if(-1 == (fd = mkstemp(*tempname))) error_exit("no temp file");
784
785 // Set permissions of output file
786
787 fstat(fdin, &statbuf);
788 fchmod(fd, statbuf.st_mode);
789
790 return fd;
791}
792
793// Abort the copy and delete the temporary file.
794void delete_tempfile(int fdin, int fdout, char **tempname)
795{
796 close(fdin);
797 close(fdout);
798 unlink(*tempname);
799 free(*tempname);
800 *tempname = NULL;
801}
802
803// Copy the rest of the data and replace the original with the copy.
804void replace_tempfile(int fdin, int fdout, char **tempname)
805{
806 char *temp = xstrdup(*tempname);
807
808 temp[strlen(temp)-6]=0;
809 if (fdin != -1) {
810 xsendfile(fdin, fdout);
811 xclose(fdin);
812 }
813 xclose(fdout);
814 rename(*tempname, temp);
815 free(*tempname);
816 free(temp);
817 *tempname = NULL;
Rob Landleybc078652007-12-15 21:47:25 -0600818}
Rob Landley7e849c52009-01-03 18:15:18 -0600819
820// Create a 256 entry CRC32 lookup table.
821
Rob Landleyb15b8fa2009-01-05 01:05:43 -0600822void crc_init(unsigned int *crc_table, int little_endian)
Rob Landley7e849c52009-01-03 18:15:18 -0600823{
824 unsigned int i;
825
826 // Init the CRC32 table (big endian)
827 for (i=0; i<256; i++) {
Rob Landleyb15b8fa2009-01-05 01:05:43 -0600828 unsigned int j, c = little_endian ? i : i<<24;
Rob Landley7e849c52009-01-03 18:15:18 -0600829 for (j=8; j; j--)
Rob Landleyb15b8fa2009-01-05 01:05:43 -0600830 if (little_endian) c = (c&1) ? (c>>1)^0xEDB88320 : c>>1;
831 else c=c&0x80000000 ? (c<<1)^0x04c11db7 : (c<<1);
Rob Landley7e849c52009-01-03 18:15:18 -0600832 crc_table[i] = c;
833 }
834}
Rob Landley26e7b5e2012-02-02 07:27:35 -0600835
836// Quick and dirty query size of terminal, doesn't do ANSI probe fallback.
837// set *x=0 and *y=0 before calling to detect failure to set either, or
838// x=80 y=25 to provide defaults
839
840void terminal_size(unsigned *x, unsigned *y)
841{
842 struct winsize ws;
843 int i;
844
845 //memset(&ws, 0, sizeof(ws));
846 for (i=0; i<3; i++) {
847 if (ioctl(i, TIOCGWINSZ, &ws)) continue;
848 if (x) *x = ws.ws_col;
849 if (y) *y = ws.ws_row;
850 }
851 if (x) {
852 char *s = getenv("COLUMNS");
853
854 i = s ? atoi(s) : 0;
855 if (i>0) *x = i;
856 }
857 if (y) {
858 char *s = getenv("ROWS");
859
860 i = s ? atoi(s) : 0;
861 if (i>0) *y = i;
862 }
863}
864
865// This should use a raw tty, fixit later.
Rob Landleyf793d532012-02-27 21:56:49 -0600866int yesno(char *prompt, int def)
Rob Landley26e7b5e2012-02-02 07:27:35 -0600867{
Rob Landleyf78c63a2012-03-20 11:10:54 -0500868 FILE *fps[] = {stdin, stdout, stderr};
869 int i;
Rob Landleyf793d532012-02-27 21:56:49 -0600870 char buf;
Rob Landley26e7b5e2012-02-02 07:27:35 -0600871
Rob Landleyf78c63a2012-03-20 11:10:54 -0500872 for (i=0; i<3; i++) if (isatty(i)) break;
873 if (i == 3) return 1;
Rob Landley26e7b5e2012-02-02 07:27:35 -0600874
Rob Landleyf78c63a2012-03-20 11:10:54 -0500875 fprintf(fps[i], "%s (%c/%c):", prompt, def ? 'Y' : 'y', def ? 'n' : 'N');
876 fflush(fps[i]);
877 while (fread(&buf, 1, 1, fps[i])) {
Rob Landleyee00a7f2012-03-19 19:19:21 -0500878 if (tolower(buf) == 'y') def = 1;
879 if (tolower(buf) == 'n') def = 0;
880 else if (!isspace(buf)) continue;
881
882 break;
Rob Landley26e7b5e2012-02-02 07:27:35 -0600883 }
Rob Landleyee00a7f2012-03-19 19:19:21 -0500884
Rob Landley26e7b5e2012-02-02 07:27:35 -0600885 return def;
886}
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600887
888// Execute a callback for each PID that matches a process name from a list.
Rob Landleyf42e11b2012-02-18 18:09:14 -0600889void for_each_pid_with_name_in(char **names, void (*callback)(pid_t pid))
890{
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600891 DIR *dp;
892 struct dirent *entry;
Rob Landley285019a2012-06-22 22:14:13 -0500893 char cmd[sizeof(toybuf)], path[64];
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600894 char **curname;
895
Rob Landleyf42e11b2012-02-18 18:09:14 -0600896 if (!(dp = opendir("/proc"))) perror_exit("opendir");
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600897
898 while ((entry = readdir(dp))) {
Rob Landley285019a2012-06-22 22:14:13 -0500899 int fd, n;
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600900
Rob Landleyf42e11b2012-02-18 18:09:14 -0600901 if (!isdigit(*entry->d_name)) continue;
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600902
Rob Landleyf42e11b2012-02-18 18:09:14 -0600903 if (sizeof(path) <= snprintf(path, sizeof(path), "/proc/%s/cmdline",
904 entry->d_name)) continue;
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600905
Rob Landley285019a2012-06-22 22:14:13 -0500906 if (-1 == (fd=open(path, O_RDONLY))) continue;
907 n = read(fd, cmd, sizeof(cmd));
908 close(fd);
909 if (n<1) continue;
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600910
Rob Landley285019a2012-06-22 22:14:13 -0500911 for (curname = names; *curname; curname++)
912 if (!strcmp(basename(cmd), *curname))
913 callback(atol(entry->d_name));
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600914 }
915
916 closedir(dp);
Rob Landleyff9ee8f2012-02-18 15:12:41 -0600917}
Rob Landley2dd50ad2012-02-26 13:48:00 -0600918
919struct signame {
920 int num;
921 char *name;
922};
923
924// Signals required by POSIX 2008:
925// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html
926
927#define SIGNIFY(x) {SIG##x, #x}
928
929static struct signame signames[] = {
930 SIGNIFY(ABRT), SIGNIFY(ALRM), SIGNIFY(BUS), SIGNIFY(CHLD), SIGNIFY(CONT),
931 SIGNIFY(FPE), SIGNIFY(HUP), SIGNIFY(ILL), SIGNIFY(INT), SIGNIFY(KILL),
932 SIGNIFY(PIPE), SIGNIFY(QUIT), SIGNIFY(SEGV), SIGNIFY(STOP), SIGNIFY(TERM),
933 SIGNIFY(TSTP), SIGNIFY(TTIN), SIGNIFY(TTOU), SIGNIFY(USR1), SIGNIFY(USR2),
934 SIGNIFY(SYS), SIGNIFY(TRAP), SIGNIFY(URG), SIGNIFY(VTALRM), SIGNIFY(XCPU),
935 SIGNIFY(XFSZ)
936};
937
938// not in posix: SIGNIFY(STKFLT), SIGNIFY(WINCH), SIGNIFY(IO), SIGNIFY(PWR)
939// obsolete: SIGNIFY(PROF) SIGNIFY(POLL)
940
941// Convert name to signal number. If name == NULL print names.
942int sig_to_num(char *pidstr)
943{
944 int i;
945
946 if (pidstr) {
947 char *s;
948 i = strtol(pidstr, &s, 10);
949 if (!*s) return i;
950
951 if (!strncasecmp(pidstr, "sig", 3)) pidstr+=3;
952 }
953 for (i = 0; i < sizeof(signames)/sizeof(struct signame); i++)
954 if (!pidstr) xputs(signames[i].name);
955 else if (!strcasecmp(pidstr, signames[i].name))
956 return signames[i].num;
957
958 return -1;
959}
960
961char *num_to_sig(int sig)
962{
963 int i;
964
965 for (i=0; i<sizeof(signames)/sizeof(struct signame); i++)
966 if (signames[i].num == sig) return signames[i].name;
967 return NULL;
968}
Daniel Walter05744b32012-03-19 19:57:56 -0500969
970
Rob Landleyb6f601e2012-05-16 21:11:43 -0500971mode_t string_to_mode(char *modestr, mode_t mode)
Daniel Walter05744b32012-03-19 19:57:56 -0500972{
Rob Landley67a069d2012-06-03 00:32:12 -0500973 char *whos = "ogua", *hows = "=+-", *whats = "xwrstX", *whys = "ogu";
Rob Landleyb6f601e2012-05-16 21:11:43 -0500974 char *s, *str = modestr;
Rob Landleycf6bcb22012-03-19 20:56:18 -0500975
Rob Landleyb6f601e2012-05-16 21:11:43 -0500976 // Handle octal mode
977 if (isdigit(*str)) {
978 mode = strtol(str, &s, 8);
979 if (*s || (mode & ~(07777))) goto barf;
980
981 return mode;
Daniel Walter05744b32012-03-19 19:57:56 -0500982 }
Rob Landleycf6bcb22012-03-19 20:56:18 -0500983
Rob Landleyb6f601e2012-05-16 21:11:43 -0500984 // Gaze into the bin of permission...
985 for (;;) {
986 int i, j, dowho, dohow, dowhat;
Rob Landleycf6bcb22012-03-19 20:56:18 -0500987
Rob Landleyb6f601e2012-05-16 21:11:43 -0500988 dowho = dohow = dowhat = 0;
Daniel Walter05744b32012-03-19 19:57:56 -0500989
Rob Landleyb6f601e2012-05-16 21:11:43 -0500990 // Find the who, how, and what stanzas, in that order
Rob Landley67a069d2012-06-03 00:32:12 -0500991 while (*str && (s = strchr(whos, *str))) {
992 dowho |= 1<<(s-whos);
Rob Landleyb6f601e2012-05-16 21:11:43 -0500993 str++;
Daniel Walter05744b32012-03-19 19:57:56 -0500994 }
Rob Landleyb6f601e2012-05-16 21:11:43 -0500995 if (!dowho) dowho = 8;
Rob Landley67a069d2012-06-03 00:32:12 -0500996 if (!*str || !(s = strchr(hows, *str))) goto barf;
Rob Landleyb6f601e2012-05-16 21:11:43 -0500997 dohow = *(str++);
Rob Landley67a069d2012-06-03 00:32:12 -0500998
Rob Landleyb6f601e2012-05-16 21:11:43 -0500999 if (!dohow) goto barf;
Rob Landley67a069d2012-06-03 00:32:12 -05001000 while (*str && (s = strchr(whats, *str))) {
1001 dowhat |= 1<<(s-whats);
Rob Landleyb6f601e2012-05-16 21:11:43 -05001002 str++;
1003 }
1004
1005 // Convert X to x for directory or if already executable somewhere
1006 if ((dowhat&32) && (S_ISDIR(mode) || (mode&0111))) dowhat |= 1;
1007
1008 // Copy mode from another category?
Rob Landley67a069d2012-06-03 00:32:12 -05001009 if (!dowhat && *str && (s = strchr(whys, *str))) {
1010 dowhat = (mode>>(3*(s-whys)))&7;
Rob Landleyb6f601e2012-05-16 21:11:43 -05001011 str++;
1012 }
1013
1014 // Are we ready to do a thing yet?
1015 if (*str && *(str++) != ',') goto barf;
1016
1017 // Ok, apply the bits to the mode.
1018 for (i=0; i<4; i++) {
1019 for (j=0; j<3; j++) {
1020 mode_t bit = 0;
1021
1022 // Figure out new value at this location
1023 if (i == 3) {
1024 } else if (dowhat&(1<<j)) bit++;
1025
1026 // When selection active, modify bit
1027 if (dowho&(8|(1<<i))) {
1028 int where = 1<<((3*i)+j);
1029
1030 if (dohow == '=' || (bit && dohow == '-'))
1031 mode &= ~where;
1032 if (bit && dohow != '-') mode |= where;
1033 }
1034 }
1035 }
1036
1037 if (!*str) break;
Daniel Walter05744b32012-03-19 19:57:56 -05001038 }
Rob Landleyb6f601e2012-05-16 21:11:43 -05001039 return mode;
1040barf:
1041 error_exit("bad mode '%s'", modestr);
Daniel Walter05744b32012-03-19 19:57:56 -05001042}