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