Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
// OSObject.h - os object for exposing posix system calls in the JS shell
8
9
#include "shell/OSObject.h"
10
11
#include "mozilla/TextUtils.h"
12
13
#include <errno.h>
14
#include <stdlib.h>
15
#ifdef XP_WIN
16
# include <direct.h>
17
# include <process.h>
18
# include <string.h>
19
#else
20
# include <sys/wait.h>
21
# include <unistd.h>
22
#endif
23
24
#include "jsapi.h"
25
// For JSFunctionSpecWithHelp
26
#include "jsfriendapi.h"
27
28
#include "gc/FreeOp.h"
29
#include "js/CharacterEncoding.h"
30
#include "js/Conversions.h"
31
#include "js/PropertySpec.h"
32
#include "js/Wrapper.h"
33
#include "shell/jsshell.h"
34
#include "util/StringBuffer.h"
35
#include "util/Text.h"
36
#include "util/Windows.h"
37
#include "vm/JSObject.h"
38
#include "vm/TypedArrayObject.h"
39
40
#include "vm/JSObject-inl.h"
41
42
#ifdef XP_WIN
43
# ifndef PATH_MAX
44
# define PATH_MAX (MAX_PATH > _MAX_DIR ? MAX_PATH : _MAX_DIR)
45
# endif
46
# define getcwd _getcwd
47
#else
48
# include <libgen.h>
49
#endif
50
51
using js::shell::RCFile;
52
53
namespace js {
54
namespace shell {
55
56
#ifdef XP_WIN
57
const char PathSeparator = '\\';
58
#else
59
const char PathSeparator = '/';
60
#endif
61
62
static bool IsAbsolutePath(const UniqueChars& filename) {
63
const char* pathname = filename.get();
64
65
if (pathname[0] == PathSeparator) {
66
return true;
67
}
68
69
#ifdef XP_WIN
70
// On Windows there are various forms of absolute paths (see
72
// for details):
73
//
74
// "\..."
75
// "\\..."
76
// "C:\..."
77
//
78
// The first two cases are handled by the test above so we only need a test
79
// for the last one here.
80
81
if ((strlen(pathname) > 3 && mozilla::IsAsciiAlpha(pathname[0]) &&
82
pathname[1] == ':' && pathname[2] == '\\')) {
83
return true;
84
}
85
#endif
86
87
return false;
88
}
89
90
/*
91
* Resolve a (possibly) relative filename to an absolute path. If
92
* |scriptRelative| is true, then the result will be relative to the directory
93
* containing the currently-running script, or the current working directory if
94
* the currently-running script is "-e" (namely, you're using it from the
95
* command line.) Otherwise, it will be relative to the current working
96
* directory.
97
*/
98
JSString* ResolvePath(JSContext* cx, HandleString filenameStr,
99
PathResolutionMode resolveMode) {
100
if (!filenameStr) {
101
#ifdef XP_WIN
102
return JS_NewStringCopyZ(cx, "nul");
103
#else
104
return JS_NewStringCopyZ(cx, "/dev/null");
105
#endif
106
}
107
108
UniqueChars filename = JS_EncodeStringToLatin1(cx, filenameStr);
109
if (!filename) {
110
return nullptr;
111
}
112
113
if (IsAbsolutePath(filename)) {
114
return filenameStr;
115
}
116
117
JS::AutoFilename scriptFilename;
118
if (resolveMode == ScriptRelative) {
119
// Get the currently executing script's name.
120
if (!DescribeScriptedCaller(cx, &scriptFilename)) {
121
return nullptr;
122
}
123
124
if (!scriptFilename.get()) {
125
return nullptr;
126
}
127
128
if (strcmp(scriptFilename.get(), "-e") == 0 ||
129
strcmp(scriptFilename.get(), "typein") == 0) {
130
resolveMode = RootRelative;
131
}
132
}
133
134
char buffer[PATH_MAX + 1];
135
if (resolveMode == ScriptRelative) {
136
#ifdef XP_WIN
137
// The docs say it can return EINVAL, but the compiler says it's void
138
_splitpath(scriptFilename.get(), nullptr, buffer, nullptr, nullptr);
139
#else
140
strncpy(buffer, scriptFilename.get(), PATH_MAX + 1);
141
if (buffer[PATH_MAX] != '\0') {
142
return nullptr;
143
}
144
145
// dirname(buffer) might return buffer, or it might return a
146
// statically-allocated string
147
memmove(buffer, dirname(buffer), strlen(buffer) + 1);
148
#endif
149
} else {
150
const char* cwd = getcwd(buffer, PATH_MAX);
151
if (!cwd) {
152
return nullptr;
153
}
154
}
155
156
size_t len = strlen(buffer);
157
buffer[len] = '/';
158
strncpy(buffer + len + 1, filename.get(), sizeof(buffer) - (len + 1));
159
if (buffer[PATH_MAX] != '\0') {
160
return nullptr;
161
}
162
163
return JS_NewStringCopyZ(cx, buffer);
164
}
165
166
JSObject* FileAsTypedArray(JSContext* cx, JS::HandleString pathnameStr) {
167
UniqueChars pathname = JS_EncodeStringToLatin1(cx, pathnameStr);
168
if (!pathname) {
169
return nullptr;
170
}
171
172
FILE* file = fopen(pathname.get(), "rb");
173
if (!file) {
174
/*
175
* Use Latin1 variant here because the encoding of the return value of
176
* strerror function can be non-UTF-8.
177
*/
178
JS_ReportErrorLatin1(cx, "can't open %s: %s", pathname.get(),
179
strerror(errno));
180
return nullptr;
181
}
182
AutoCloseFile autoClose(file);
183
184
RootedObject obj(cx);
185
if (fseek(file, 0, SEEK_END) != 0) {
186
pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
187
if (!pathname) {
188
return nullptr;
189
}
190
JS_ReportErrorUTF8(cx, "can't seek end of %s", pathname.get());
191
} else {
192
size_t len = ftell(file);
193
if (fseek(file, 0, SEEK_SET) != 0) {
194
pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
195
if (!pathname) {
196
return nullptr;
197
}
198
JS_ReportErrorUTF8(cx, "can't seek start of %s", pathname.get());
199
} else {
200
if (len > ArrayBufferObject::MaxBufferByteLength) {
201
JS_ReportErrorUTF8(cx, "file %s is too large for a Uint8Array",
202
pathname.get());
203
return nullptr;
204
}
205
obj = JS_NewUint8Array(cx, len);
206
if (!obj) {
207
return nullptr;
208
}
209
js::TypedArrayObject& ta = obj->as<js::TypedArrayObject>();
210
if (ta.isSharedMemory()) {
211
// Must opt in to use shared memory. For now, don't.
212
//
213
// (It is incorrect to read into the buffer without
214
// synchronization since that can create a race. A
215
// lock here won't fix it - both sides must
216
// participate. So what one must do is to create a
217
// temporary buffer, read into that, and use a
218
// race-safe primitive to copy memory into the
219
// buffer.)
220
pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
221
if (!pathname) {
222
return nullptr;
223
}
224
JS_ReportErrorUTF8(cx, "can't read %s: shared memory buffer",
225
pathname.get());
226
return nullptr;
227
}
228
char* buf = static_cast<char*>(ta.dataPointerUnshared());
229
size_t cc = fread(buf, 1, len, file);
230
if (cc != len) {
231
if (ptrdiff_t(cc) < 0) {
232
/*
233
* Use Latin1 variant here because the encoding of the return
234
* value of strerror function can be non-UTF-8.
235
*/
236
JS_ReportErrorLatin1(cx, "can't read %s: %s", pathname.get(),
237
strerror(errno));
238
} else {
239
pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
240
if (!pathname) {
241
return nullptr;
242
}
243
JS_ReportErrorUTF8(cx, "can't read %s: short read", pathname.get());
244
}
245
obj = nullptr;
246
}
247
}
248
}
249
return obj;
250
}
251
252
/**
253
* Return the current working directory or |null| on failure.
254
*/
255
UniqueChars GetCWD() {
256
char buffer[PATH_MAX + 1];
257
const char* cwd = getcwd(buffer, PATH_MAX);
258
if (!cwd) {
259
return UniqueChars();
260
}
261
return js::DuplicateString(buffer);
262
}
263
264
static bool ReadFile(JSContext* cx, unsigned argc, Value* vp,
265
bool scriptRelative) {
266
CallArgs args = CallArgsFromVp(argc, vp);
267
268
if (args.length() < 1 || args.length() > 2) {
269
JS_ReportErrorNumberASCII(
270
cx, js::shell::my_GetErrorMessage, nullptr,
271
args.length() < 1 ? JSSMSG_NOT_ENOUGH_ARGS : JSSMSG_TOO_MANY_ARGS,
272
"snarf");
273
return false;
274
}
275
276
if (!args[0].isString() || (args.length() == 2 && !args[1].isString())) {
277
JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr,
278
JSSMSG_INVALID_ARGS, "snarf");
279
return false;
280
}
281
282
RootedString givenPath(cx, args[0].toString());
283
RootedString str(
284
cx, js::shell::ResolvePath(
285
cx, givenPath, scriptRelative ? ScriptRelative : RootRelative));
286
if (!str) {
287
return false;
288
}
289
290
if (args.length() > 1) {
291
JSString* opt = JS::ToString(cx, args[1]);
292
if (!opt) {
293
return false;
294
}
295
bool match;
296
if (!JS_StringEqualsLiteral(cx, opt, "binary", &match)) {
297
return false;
298
}
299
if (match) {
300
JSObject* obj;
301
if (!(obj = FileAsTypedArray(cx, str))) {
302
return false;
303
}
304
args.rval().setObject(*obj);
305
return true;
306
}
307
}
308
309
if (!(str = FileAsString(cx, str))) {
310
return false;
311
}
312
args.rval().setString(str);
313
return true;
314
}
315
316
static bool osfile_readFile(JSContext* cx, unsigned argc, Value* vp) {
317
return ReadFile(cx, argc, vp, false);
318
}
319
320
static bool osfile_readRelativeToScript(JSContext* cx, unsigned argc,
321
Value* vp) {
322
return ReadFile(cx, argc, vp, true);
323
}
324
325
static bool osfile_writeTypedArrayToFile(JSContext* cx, unsigned argc,
326
Value* vp) {
327
CallArgs args = CallArgsFromVp(argc, vp);
328
329
if (args.length() != 2 || !args[0].isString() || !args[1].isObject() ||
330
!args[1].toObject().is<TypedArrayObject>()) {
331
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
332
JSSMSG_INVALID_ARGS, "writeTypedArrayToFile");
333
return false;
334
}
335
336
RootedString givenPath(cx, args[0].toString());
337
RootedString str(cx, ResolvePath(cx, givenPath, RootRelative));
338
if (!str) {
339
return false;
340
}
341
342
UniqueChars filename = JS_EncodeStringToLatin1(cx, str);
343
if (!filename) {
344
return false;
345
}
346
347
FILE* file = fopen(filename.get(), "wb");
348
if (!file) {
349
/*
350
* Use Latin1 variant here because the encoding of the return value of
351
* strerror function can be non-UTF-8.
352
*/
353
JS_ReportErrorLatin1(cx, "can't open %s: %s", filename.get(),
354
strerror(errno));
355
return false;
356
}
357
AutoCloseFile autoClose(file);
358
359
TypedArrayObject* obj = &args[1].toObject().as<TypedArrayObject>();
360
361
if (obj->isSharedMemory()) {
362
// Must opt in to use shared memory. For now, don't.
363
//
364
// See further comments in FileAsTypedArray, above.
365
filename = JS_EncodeStringToUTF8(cx, str);
366
if (!filename) {
367
return false;
368
}
369
JS_ReportErrorUTF8(cx, "can't write %s: shared memory buffer",
370
filename.get());
371
return false;
372
}
373
void* buf = obj->dataPointerUnshared();
374
if (fwrite(buf, obj->bytesPerElement(), obj->length(), file) !=
375
obj->length() ||
376
!autoClose.release()) {
377
filename = JS_EncodeStringToUTF8(cx, str);
378
if (!filename) {
379
return false;
380
}
381
JS_ReportErrorUTF8(cx, "can't write %s", filename.get());
382
return false;
383
}
384
385
args.rval().setUndefined();
386
return true;
387
}
388
389
/* static */
390
RCFile* RCFile::create(JSContext* cx, const char* filename, const char* mode) {
391
FILE* fp = fopen(filename, mode);
392
if (!fp) {
393
return nullptr;
394
}
395
396
RCFile* file = cx->new_<RCFile>(fp);
397
if (!file) {
398
fclose(fp);
399
return nullptr;
400
}
401
402
return file;
403
}
404
405
void RCFile::close() {
406
if (fp) {
407
fclose(fp);
408
}
409
fp = nullptr;
410
}
411
412
bool RCFile::release() {
413
if (--numRefs) {
414
return false;
415
}
416
this->close();
417
return true;
418
}
419
420
class FileObject : public NativeObject {
421
enum : uint32_t { FILE_SLOT = 0, NUM_SLOTS };
422
423
public:
424
static const JSClass class_;
425
426
static FileObject* create(JSContext* cx, RCFile* file) {
427
FileObject* obj = js::NewBuiltinClassInstance<FileObject>(cx);
428
if (!obj) {
429
return nullptr;
430
}
431
432
InitReservedSlot(obj, FILE_SLOT, file, MemoryUse::FileObjectFile);
433
file->acquire();
434
return obj;
435
}
436
437
static void finalize(JSFreeOp* fop, JSObject* obj) {
438
FileObject* fileObj = &obj->as<FileObject>();
439
RCFile* file = fileObj->rcFile();
440
fop->removeCellMemory(obj, sizeof(*file), MemoryUse::FileObjectFile);
441
if (file->release()) {
442
fop->deleteUntracked(file);
443
}
444
}
445
446
bool isOpen() {
447
RCFile* file = rcFile();
448
return file && file->isOpen();
449
}
450
451
void close() {
452
if (!isOpen()) {
453
return;
454
}
455
rcFile()->close();
456
}
457
458
RCFile* rcFile() {
459
return reinterpret_cast<RCFile*>(
460
js::GetReservedSlot(this, FILE_SLOT).toPrivate());
461
}
462
};
463
464
static const JSClassOps FileObjectClassOps = {
465
nullptr, /* addProperty */
466
nullptr, /* delProperty */
467
nullptr, /* enumerate */
468
nullptr, /* newEnumerate */
469
nullptr, /* resolve */
470
nullptr, /* mayResolve */
471
FileObject::finalize, /* finalize */
472
nullptr, /* call */
473
nullptr, /* hasInstance */
474
nullptr, /* construct */
475
nullptr /* trace */
476
};
477
478
const JSClass FileObject::class_ = {
479
"File",
480
JSCLASS_HAS_RESERVED_SLOTS(FileObject::NUM_SLOTS) |
481
JSCLASS_FOREGROUND_FINALIZE,
482
&FileObjectClassOps};
483
484
static FileObject* redirect(JSContext* cx, HandleString relFilename,
485
RCFile** globalFile) {
486
RootedString filename(cx, ResolvePath(cx, relFilename, RootRelative));
487
if (!filename) {
488
return nullptr;
489
}
490
UniqueChars filenameABS = JS_EncodeStringToLatin1(cx, filename);
491
if (!filenameABS) {
492
return nullptr;
493
}
494
RCFile* file = RCFile::create(cx, filenameABS.get(), "wb");
495
if (!file) {
496
/*
497
* Use Latin1 variant here because the encoding of the return value of
498
* strerror function can be non-UTF-8.
499
*/
500
JS_ReportErrorLatin1(cx, "cannot redirect to %s: %s", filenameABS.get(),
501
strerror(errno));
502
return nullptr;
503
}
504
505
// Grant the global gOutFile ownership of the new file, release ownership
506
// of its old file, and return a FileObject owning the old file.
507
file->acquire(); // Global owner of new file
508
509
FileObject* fileObj =
510
FileObject::create(cx, *globalFile); // Newly created owner of old file
511
if (!fileObj) {
512
file->release();
513
return nullptr;
514
}
515
516
(*globalFile)->release(); // Release (global) ownership of old file.
517
*globalFile = file;
518
519
return fileObj;
520
}
521
522
static bool Redirect(JSContext* cx, const CallArgs& args, RCFile** outFile) {
523
if (args.length() > 1) {
524
JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr,
525
JSSMSG_INVALID_ARGS, "redirect");
526
return false;
527
}
528
529
RCFile* oldFile = *outFile;
530
RootedObject oldFileObj(cx, FileObject::create(cx, oldFile));
531
if (!oldFileObj) {
532
return false;
533
}
534
535
if (args.get(0).isUndefined()) {
536
args.rval().setObject(*oldFileObj);
537
return true;
538
}
539
540
if (args[0].isObject()) {
541
Rooted<FileObject*> fileObj(cx,
542
args[0].toObject().maybeUnwrapIf<FileObject>());
543
if (!fileObj) {
544
JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr,
545
JSSMSG_INVALID_ARGS, "redirect");
546
return false;
547
}
548
549
// Passed in a FileObject. Create a FileObject for the previous
550
// global file, and set the global file to the passed-in one.
551
*outFile = fileObj->rcFile();
552
(*outFile)->acquire();
553
oldFile->release();
554
555
args.rval().setObject(*oldFileObj);
556
return true;
557
}
558
559
RootedString filename(cx);
560
if (!args[0].isNull()) {
561
filename = JS::ToString(cx, args[0]);
562
if (!filename) {
563
return false;
564
}
565
}
566
567
if (!redirect(cx, filename, outFile)) {
568
return false;
569
}
570
571
args.rval().setObject(*oldFileObj);
572
return true;
573
}
574
575
static bool osfile_redirectOutput(JSContext* cx, unsigned argc, Value* vp) {
576
CallArgs args = CallArgsFromVp(argc, vp);
577
ShellContext* scx = GetShellContext(cx);
578
return Redirect(cx, args, scx->outFilePtr);
579
}
580
581
static bool osfile_redirectError(JSContext* cx, unsigned argc, Value* vp) {
582
CallArgs args = CallArgsFromVp(argc, vp);
583
ShellContext* scx = GetShellContext(cx);
584
return Redirect(cx, args, scx->errFilePtr);
585
}
586
587
static bool osfile_close(JSContext* cx, unsigned argc, Value* vp) {
588
CallArgs args = CallArgsFromVp(argc, vp);
589
590
Rooted<FileObject*> fileObj(cx);
591
if (args.get(0).isObject()) {
592
fileObj = args[0].toObject().maybeUnwrapIf<FileObject>();
593
}
594
595
if (!fileObj) {
596
JS_ReportErrorNumberASCII(cx, js::shell::my_GetErrorMessage, nullptr,
597
JSSMSG_INVALID_ARGS, "close");
598
return false;
599
}
600
601
fileObj->close();
602
603
args.rval().setUndefined();
604
return true;
605
}
606
607
// clang-format off
608
static const JSFunctionSpecWithHelp osfile_functions[] = {
609
JS_FN_HELP("readFile", osfile_readFile, 1, 0,
610
"readFile(filename, [\"binary\"])",
611
" Read entire contents of filename. Returns a string, unless \"binary\" is passed\n"
612
" as the second argument, in which case it returns a Uint8Array. Filename is\n"
613
" relative to the current working directory."),
614
615
JS_FN_HELP("readRelativeToScript", osfile_readRelativeToScript, 1, 0,
616
"readRelativeToScript(filename, [\"binary\"])",
617
" Read filename into returned string. Filename is relative to the directory\n"
618
" containing the current script."),
619
620
JS_FS_HELP_END
621
};
622
// clang-format on
623
624
// clang-format off
625
static const JSFunctionSpecWithHelp osfile_unsafe_functions[] = {
626
JS_FN_HELP("writeTypedArrayToFile", osfile_writeTypedArrayToFile, 2, 0,
627
"writeTypedArrayToFile(filename, data)",
628
" Write the contents of a typed array to the named file."),
629
630
JS_FN_HELP("redirect", osfile_redirectOutput, 1, 0,
631
"redirect([path-or-object])",
632
" Redirect print() output to the named file.\n"
633
" Return an opaque object representing the previous destination, which\n"
634
" may be passed into redirect() later to restore the output."),
635
636
JS_FN_HELP("redirectErr", osfile_redirectError, 1, 0,
637
"redirectErr([path-or-object])",
638
" Same as redirect(), but for printErr"),
639
640
JS_FN_HELP("close", osfile_close, 1, 0,
641
"close(object)",
642
" Close the file returned by an earlier redirect call."),
643
644
JS_FS_HELP_END
645
};
646
// clang-format on
647
648
static bool ospath_isAbsolute(JSContext* cx, unsigned argc, Value* vp) {
649
CallArgs args = CallArgsFromVp(argc, vp);
650
651
if (args.length() != 1 || !args[0].isString()) {
652
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
653
JSSMSG_INVALID_ARGS, "isAbsolute");
654
return false;
655
}
656
657
UniqueChars path = JS_EncodeStringToLatin1(cx, args[0].toString());
658
if (!path) {
659
return false;
660
}
661
662
args.rval().setBoolean(IsAbsolutePath(path));
663
return true;
664
}
665
666
static bool ospath_join(JSContext* cx, unsigned argc, Value* vp) {
667
CallArgs args = CallArgsFromVp(argc, vp);
668
669
if (args.length() < 1) {
670
JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
671
JSSMSG_INVALID_ARGS, "join");
672
return false;
673
}
674
675
// This function doesn't take into account some aspects of Windows paths,
676
// e.g. the drive letter is always reset when an absolute path is appended.
677
678
JSStringBuilder buffer(cx);
679
680
for (unsigned i = 0; i < args.length(); i++) {
681
if (!args[i].isString()) {
682
JS_ReportErrorASCII(cx, "join expects string arguments only");
683
return false;
684
}
685
686
UniqueChars path = JS_EncodeStringToLatin1(cx, args[i].toString());
687
if (!path) {
688
return false;
689
}
690
691
if (IsAbsolutePath(path)) {
692
MOZ_ALWAYS_TRUE(buffer.resize(0));
693
} else if (i != 0) {
694
if (!buffer.append(PathSeparator)) {
695
return false;
696
}
697
}
698
699
if (!buffer.append(args[i].toString())) {
700
return false;
701
}
702
}
703
704
JSString* result = buffer.finishString();
705
if (!result) {
706
return false;
707
}
708
709
args.rval().setString(result);
710
return true;
711
}
712
713
// clang-format off
714
static const JSFunctionSpecWithHelp ospath_functions[] = {
715
JS_FN_HELP("isAbsolute", ospath_isAbsolute, 1, 0,
716
"isAbsolute(path)",
717
" Return whether the given path is absolute."),
718
719
JS_FN_HELP("join", ospath_join, 1, 0,
720
"join(paths...)",
721
" Join one or more path components in a platform independent way."),
722
723
JS_FS_HELP_END
724
};
725
// clang-format on
726
727
static bool os_getenv(JSContext* cx, unsigned argc, Value* vp) {
728
CallArgs args = CallArgsFromVp(argc, vp);
729
if (args.length() < 1) {
730
JS_ReportErrorASCII(cx, "os.getenv requires 1 argument");
731
return false;
732
}
733
RootedString key(cx, ToString(cx, args[0]));
734
if (!key) {
735
return false;
736
}
737
UniqueChars keyBytes = JS_EncodeStringToUTF8(cx, key);
738
if (!keyBytes) {
739
return false;
740
}
741
742
if (const char* valueBytes = getenv(keyBytes.get())) {
743
RootedString value(cx, JS_NewStringCopyZ(cx, valueBytes));
744
if (!value) {
745
return false;
746
}
747
args.rval().setString(value);
748
} else {
749
args.rval().setUndefined();
750
}
751
return true;
752
}
753
754
static bool os_getpid(JSContext* cx, unsigned argc, Value* vp) {
755
CallArgs args = CallArgsFromVp(argc, vp);
756
if (args.length() != 0) {
757
JS_ReportErrorASCII(cx, "os.getpid takes no arguments");
758
return false;
759
}
760
args.rval().setInt32(getpid());
761
return true;
762
}
763
764
#if !defined(XP_WIN)
765
766
// There are two possible definitions of strerror_r floating around. The GNU
767
// one returns a char* which may or may not be the buffer you passed in. The
768
// other one returns an integer status code, and always writes the result into
769
// the provided buffer.
770
771
inline char* strerror_message(int result, char* buffer) {
772
return result == 0 ? buffer : nullptr;
773
}
774
775
inline char* strerror_message(char* result, char* buffer) { return result; }
776
777
#endif
778
779
static void ReportSysError(JSContext* cx, const char* prefix) {
780
char buffer[200];
781
782
#if defined(XP_WIN)
783
strerror_s(buffer, sizeof(buffer), errno);
784
const char* errstr = buffer;
785
#else
786
const char* errstr =
787
strerror_message(strerror_r(errno, buffer, sizeof(buffer)), buffer);
788
#endif
789
790
if (!errstr) {
791
errstr = "unknown error";
792
}
793
794
/*
795
* Use Latin1 variant here because the encoding of the return value of
796
* strerror_s and strerror_r function can be non-UTF-8.
797
*/
798
JS_ReportErrorLatin1(cx, "%s: %s", prefix, errstr);
799
}
800
801
static bool os_system(JSContext* cx, unsigned argc, Value* vp) {
802
CallArgs args = CallArgsFromVp(argc, vp);
803
804
if (args.length() == 0) {
805
JS_ReportErrorASCII(cx, "os.system requires 1 argument");
806
return false;
807
}
808
809
JSString* str = JS::ToString(cx, args[0]);
810
if (!str) {
811
return false;
812
}
813
814
UniqueChars command = JS_EncodeStringToLatin1(cx, str);
815
if (!command) {
816
return false;
817
}
818
819
int result = system(command.get());
820
if (result == -1) {
821
ReportSysError(cx, "system call failed");
822
return false;
823
}
824
825
args.rval().setInt32(result);
826
return true;
827
}
828
829
#ifndef XP_WIN
830
static bool os_spawn(JSContext* cx, unsigned argc, Value* vp) {
831
CallArgs args = CallArgsFromVp(argc, vp);
832
833
if (args.length() == 0) {
834
JS_ReportErrorASCII(cx, "os.spawn requires 1 argument");
835
return false;
836
}
837
838
JSString* str = JS::ToString(cx, args[0]);
839
if (!str) {
840
return false;
841
}
842
843
UniqueChars command = JS_EncodeStringToLatin1(cx, str);
844
if (!command) {
845
return false;
846
}
847
848
int32_t childPid = fork();
849
if (childPid == -1) {
850
ReportSysError(cx, "fork failed");
851
return false;
852
}
853
854
if (childPid) {
855
args.rval().setInt32(childPid);
856
return true;
857
}
858
859
// We are in the child
860
861
const char* cmd[] = {"sh", "-c", nullptr, nullptr};
862
cmd[2] = command.get();
863
864
execvp("sh", (char* const*)cmd);
865
exit(1);
866
}
867
868
static bool os_kill(JSContext* cx, unsigned argc, Value* vp) {
869
CallArgs args = CallArgsFromVp(argc, vp);
870
int32_t pid;
871
if (args.length() < 1) {
872
JS_ReportErrorASCII(cx, "os.kill requires 1 argument");
873
return false;
874
}
875
if (!JS::ToInt32(cx, args[0], &pid)) {
876
return false;
877
}
878
879
// It is too easy to kill yourself accidentally with os.kill("goose").
880
if (pid == 0 && !args[0].isInt32()) {
881
JS_ReportErrorASCII(cx, "os.kill requires numeric pid");
882
return false;
883
}
884
885
int signal = SIGINT;
886
if (args.length() > 1) {
887
if (!JS::ToInt32(cx, args[1], &signal)) {
888
return false;
889
}
890
}
891
892
int status = kill(pid, signal);
893
if (status == -1) {
894
ReportSysError(cx, "kill failed");
895
return false;
896
}
897
898
args.rval().setUndefined();
899
return true;
900
}
901
902
static bool os_waitpid(JSContext* cx, unsigned argc, Value* vp) {
903
CallArgs args = CallArgsFromVp(argc, vp);
904
905
int32_t pid;
906
if (args.length() == 0) {
907
pid = -1;
908
} else {
909
if (!JS::ToInt32(cx, args[0], &pid)) {
910
return false;
911
}
912
}
913
914
bool nohang = false;
915
if (args.length() >= 2) {
916
nohang = JS::ToBoolean(args[1]);
917
}
918
919
int status = 0;
920
pid_t result = waitpid(pid, &status, nohang ? WNOHANG : 0);
921
if (result == -1) {
922
ReportSysError(cx, "os.waitpid failed");
923
return false;
924
}
925
926
RootedObject info(cx, JS_NewPlainObject(cx));
927
if (!info) {
928
return false;
929
}
930
931
RootedValue v(cx);
932
if (result != 0) {
933
v.setInt32(result);
934
if (!JS_DefineProperty(cx, info, "pid", v, JSPROP_ENUMERATE)) {
935
return false;
936
}
937
if (WIFEXITED(status)) {
938
v.setInt32(WEXITSTATUS(status));
939
if (!JS_DefineProperty(cx, info, "exitStatus", v, JSPROP_ENUMERATE)) {
940
return false;
941
}
942
}
943
}
944
945
args.rval().setObject(*info);
946
return true;
947
}
948
#endif
949
950
// clang-format off
951
static const JSFunctionSpecWithHelp os_functions[] = {
952
JS_FN_HELP("getenv", os_getenv, 1, 0,
953
"getenv(variable)",
954
" Get the value of an environment variable."),
955
956
JS_FN_HELP("getpid", os_getpid, 0, 0,
957
"getpid()",
958
" Return the current process id."),
959
960
JS_FN_HELP("system", os_system, 1, 0,
961
"system(command)",
962
" Execute command on the current host, returning result code or throwing an\n"
963
" exception on failure."),
964
965
#ifndef XP_WIN
966
JS_FN_HELP("spawn", os_spawn, 1, 0,
967
"spawn(command)",
968
" Start up a separate process running the given command. Returns the pid."),
969
970
JS_FN_HELP("kill", os_kill, 1, 0,
971
"kill(pid[, signal])",
972
" Send a signal to the given pid. The default signal is SIGINT. The signal\n"
973
" passed in must be numeric, if given."),
974
975
JS_FN_HELP("waitpid", os_waitpid, 1, 0,
976
"waitpid(pid[, nohang])",
977
" Calls waitpid(). 'nohang' is a boolean indicating whether to pass WNOHANG.\n"
978
" The return value is an object containing a 'pid' field, if a process was waitable\n"
979
" and an 'exitStatus' field if a pid exited."),
980
#endif
981
982
JS_FS_HELP_END
983
};
984
// clang-format on
985
986
bool DefineOS(JSContext* cx, HandleObject global, bool fuzzingSafe,
987
RCFile** shellOut, RCFile** shellErr) {
988
RootedObject obj(cx, JS_NewPlainObject(cx));
989
if (!obj || !JS_DefineProperty(cx, global, "os", obj, 0)) {
990
return false;
991
}
992
993
if (!fuzzingSafe) {
994
if (!JS_DefineFunctionsWithHelp(cx, obj, os_functions)) {
995
return false;
996
}
997
}
998
999
RootedObject osfile(cx, JS_NewPlainObject(cx));
1000
if (!osfile || !JS_DefineFunctionsWithHelp(cx, osfile, osfile_functions) ||
1001
!JS_DefineProperty(cx, obj, "file", osfile, 0)) {
1002
return false;
1003
}
1004
1005
if (!fuzzingSafe) {
1006
if (!JS_DefineFunctionsWithHelp(cx, osfile, osfile_unsafe_functions)) {
1007
return false;
1008
}
1009
}
1010
1011
if (!GenerateInterfaceHelp(cx, osfile, "os.file")) {
1012
return false;
1013
}
1014
1015
RootedObject ospath(cx, JS_NewPlainObject(cx));
1016
if (!ospath || !JS_DefineFunctionsWithHelp(cx, ospath, ospath_functions) ||
1017
!JS_DefineProperty(cx, obj, "path", ospath, 0) ||
1018
!GenerateInterfaceHelp(cx, ospath, "os.path")) {
1019
return false;
1020
}
1021
1022
if (!GenerateInterfaceHelp(cx, obj, "os")) {
1023
return false;
1024
}
1025
1026
ShellContext* scx = GetShellContext(cx);
1027
scx->outFilePtr = shellOut;
1028
scx->errFilePtr = shellErr;
1029
1030
// For backwards compatibility, expose various os.file.* functions as
1031
// direct methods on the global.
1032
struct Export {
1033
const char* src;
1034
const char* dst;
1035
};
1036
1037
const Export osfile_exports[] = {
1038
{"readFile", "read"},
1039
{"readFile", "snarf"},
1040
{"readRelativeToScript", "readRelativeToScript"},
1041
};
1042
1043
for (auto pair : osfile_exports) {
1044
if (!CreateAlias(cx, pair.dst, osfile, pair.src)) {
1045
return false;
1046
}
1047
}
1048
1049
if (!fuzzingSafe) {
1050
const Export unsafe_osfile_exports[] = {{"redirect", "redirect"},
1051
{"redirectErr", "redirectErr"}};
1052
1053
for (auto pair : unsafe_osfile_exports) {
1054
if (!CreateAlias(cx, pair.dst, osfile, pair.src)) {
1055
return false;
1056
}
1057
}
1058
}
1059
1060
return true;
1061
}
1062
1063
} // namespace shell
1064
} // namespace js