Skip to main content

ferrijs_std/os/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3#![allow(clippy::uninlined_format_args)]
4
5use std::env;
6
7use crate::utils::{
8    module::{export_default, ModuleInfo},
9    sysinfo::{ARCH, PLATFORM},
10};
11use rquickjs::{
12    module::{Declarations, Exports, ModuleDef},
13    prelude::Func,
14    Ctx, Exception, Object, Result,
15};
16
17#[cfg(feature = "system")]
18use sysinfo::System;
19
20#[cfg(unix)]
21use self::unix::{
22    get_priority, get_release, get_type, get_user_info, get_version, set_priority, DEV_NULL, EOL,
23};
24
25#[cfg(unix)]
26mod unix;
27
28#[cfg(windows)]
29use self::windows::{
30    get_priority, get_release, get_type, get_user_info, get_version, set_priority, DEV_NULL, EOL,
31};
32
33#[cfg(windows)]
34mod windows;
35
36#[cfg(feature = "network")]
37use self::network::get_network_interfaces;
38#[cfg(feature = "statistics")]
39use self::statistics::{get_cpus, get_free_mem, get_total_mem};
40
41#[cfg(feature = "network")]
42mod network;
43#[cfg(feature = "statistics")]
44mod statistics;
45
46fn get_available_parallelism() -> usize {
47    num_cpus::get()
48}
49
50fn get_endianness() -> &'static str {
51    #[cfg(target_endian = "little")]
52    {
53        "LE"
54    }
55    #[cfg(target_endian = "big")]
56    {
57        "BE"
58    }
59}
60
61fn get_home_dir(ctx: Ctx<'_>) -> Result<String> {
62    home::home_dir()
63        .map(|val| val.to_string_lossy().into_owned())
64        .ok_or_else(|| Exception::throw_message(&ctx, "Could not determine home directory"))
65}
66
67#[cfg(feature = "system")]
68fn get_host_name(ctx: Ctx<'_>) -> Result<String> {
69    System::host_name().ok_or_else(|| Exception::throw_reference(&ctx, "System::host_name"))
70}
71
72#[cfg(feature = "system")]
73fn get_load_avg() -> Vec<f64> {
74    let load_avg = System::load_average();
75
76    vec![load_avg.one, load_avg.five, load_avg.fifteen]
77}
78
79#[cfg(feature = "system")]
80fn get_machine() -> String {
81    System::cpu_arch()
82}
83
84fn get_tmp_dir() -> String {
85    env::temp_dir().to_string_lossy().to_string()
86}
87
88#[cfg(feature = "system")]
89fn get_uptime() -> u64 {
90    System::uptime()
91}
92
93pub struct OsModule;
94
95impl ModuleDef for OsModule {
96    fn declare(declare: &Declarations) -> Result<()> {
97        declare.declare("arch")?;
98        declare.declare("availableParallelism")?;
99        declare.declare("devNull")?;
100        declare.declare("endianness")?;
101        declare.declare("EOL")?;
102        declare.declare("getPriority")?;
103        declare.declare("homedir")?;
104        declare.declare("platform")?;
105        declare.declare("release")?;
106        declare.declare("setPriority")?;
107        declare.declare("tmpdir")?;
108        declare.declare("type")?;
109        declare.declare("userInfo")?;
110        declare.declare("version")?;
111
112        #[cfg(feature = "network")]
113        {
114            declare.declare("networkInterfaces")?;
115        }
116
117        #[cfg(feature = "statistics")]
118        {
119            declare.declare("cpus")?;
120            declare.declare("freemem")?;
121            declare.declare("totalmem")?;
122        }
123        #[cfg(feature = "system")]
124        {
125            declare.declare("hostname")?;
126            declare.declare("loadavg")?;
127            declare.declare("machine")?;
128            declare.declare("uptime")?;
129        }
130        declare.declare("default")?;
131        Ok(())
132    }
133
134    fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
135        export_default(ctx, exports, |default| {
136            fill(default)?;
137            Ok(())
138        })
139    }
140}
141
142/// Every `os` export on one object.
143///
144/// Local delta: upstream fills the module's default export inline. The
145/// embedding runtime serves the same surface twice — as an ES module and
146/// as a synchronous `require('os')` namespace — and both must read from one
147/// place, so the body moved into a function.
148pub fn fill(target: &Object<'_>) -> Result<()> {
149    // LOCAL DELTA: every member that reveals something about the host
150    // asks the realm's `sys` grant first (see `crate::permissions`).
151    // `arch` / `platform` / `EOL` / `devNull` / `endianness` / `type` /
152    // `tmpdir` / `availableParallelism` describe the binary rather than
153    // the machine it runs on and stay open.
154    use crate::permissions::check_sys;
155    use ferrijs_permissions::SysInfo;
156    use rquickjs::{function::Opt, Value};
157
158    target.set("arch", Func::from(|| ARCH))?;
159    target.set(
160        "availableParallelism",
161        Func::from(get_available_parallelism),
162    )?;
163    target.set("devNull", DEV_NULL)?;
164    target.set("endianness", Func::from(get_endianness))?;
165    target.set("EOL", EOL)?;
166    target.set(
167        "getPriority",
168        Func::from(|ctx: Ctx<'_>, who: Opt<u32>| -> Result<i32> {
169            check_sys(&ctx, SysInfo::Priority)?;
170            Ok(get_priority(who))
171        }),
172    )?;
173    target.set(
174        "homedir",
175        Func::from(|ctx: Ctx<'_>| -> Result<String> {
176            check_sys(&ctx, SysInfo::HomeDir)?;
177            get_home_dir(ctx)
178        }),
179    )?;
180    target.set("platform", Func::from(|| PLATFORM))?;
181    target.set(
182        "release",
183        Func::from(|ctx: Ctx<'_>| -> Result<&'static str> {
184            check_sys(&ctx, SysInfo::OsRelease)?;
185            Ok(get_release())
186        }),
187    )?;
188    target.set(
189        "setPriority",
190        Func::from(|ctx: Ctx<'_>, args: rquickjs::function::Rest<Value<'_>>| -> Result<()> {
191            check_sys(&ctx, SysInfo::Priority)?;
192            set_priority(ctx, args)
193        }),
194    )?;
195    target.set("tmpdir", Func::from(get_tmp_dir))?;
196    target.set("type", Func::from(get_type))?;
197    target.set("userInfo", Func::from(user_info_guarded))?;
198    target.set(
199        "version",
200        Func::from(|ctx: Ctx<'_>| -> Result<&'static str> {
201            check_sys(&ctx, SysInfo::OsRelease)?;
202            Ok(get_version())
203        }),
204    )?;
205    #[cfg(feature = "network")]
206    {
207        target.set("networkInterfaces", Func::from(network_interfaces_guarded))?;
208    }
209
210    #[cfg(feature = "statistics")]
211    {
212        target.set("cpus", Func::from(cpus_guarded))?;
213        target.set(
214            "freemem",
215            Func::from(|ctx: Ctx<'_>| -> Result<u64> {
216                check_sys(&ctx, SysInfo::SystemMemory)?;
217                Ok(get_free_mem())
218            }),
219        )?;
220        target.set(
221            "totalmem",
222            Func::from(|ctx: Ctx<'_>| -> Result<u64> {
223                check_sys(&ctx, SysInfo::SystemMemory)?;
224                Ok(get_total_mem())
225            }),
226        )?;
227    }
228    #[cfg(feature = "system")]
229    {
230        target.set(
231            "hostname",
232            Func::from(|ctx: Ctx<'_>| -> Result<String> {
233                check_sys(&ctx, SysInfo::Hostname)?;
234                get_host_name(ctx)
235            }),
236        )?;
237        target.set(
238            "loadavg",
239            Func::from(|ctx: Ctx<'_>| -> Result<Vec<f64>> {
240                check_sys(&ctx, SysInfo::LoadAvg)?;
241                Ok(get_load_avg())
242            }),
243        )?;
244        target.set(
245            "machine",
246            Func::from(|ctx: Ctx<'_>| -> Result<String> {
247                check_sys(&ctx, SysInfo::OsRelease)?;
248                Ok(get_machine())
249            }),
250        )?;
251        target.set(
252            "uptime",
253            Func::from(|ctx: Ctx<'_>| -> Result<u64> {
254                check_sys(&ctx, SysInfo::OsUptime)?;
255                Ok(get_uptime())
256            }),
257        )?;
258    }
259    Ok(())
260}
261
262fn user_info_guarded<'js>(ctx: Ctx<'js>, options: rquickjs::function::Opt<rquickjs::Value<'js>>) -> Result<Object<'js>> {
263    crate::permissions::check_sys(&ctx, ferrijs_permissions::SysInfo::Username)?;
264    get_user_info(ctx, options)
265}
266
267#[cfg(feature = "network")]
268fn network_interfaces_guarded<'js>(
269    ctx: Ctx<'js>,
270) -> Result<std::collections::HashMap<String, Vec<Object<'js>>>> {
271    crate::permissions::check_sys(&ctx, ferrijs_permissions::SysInfo::NetworkInterfaces)?;
272    get_network_interfaces(ctx)
273}
274
275#[cfg(feature = "statistics")]
276fn cpus_guarded<'js>(ctx: Ctx<'js>) -> Result<Vec<Object<'js>>> {
277    crate::permissions::check_sys(&ctx, ferrijs_permissions::SysInfo::Cpus)?;
278    get_cpus(ctx)
279}
280
281/// A fresh object carrying every `os` export, for the `require` path.
282pub fn os_object<'js>(ctx: &Ctx<'js>) -> Result<Object<'js>> {
283    let obj = Object::new(ctx.clone())?;
284    fill(&obj)?;
285    Ok(obj)
286}
287
288impl From<OsModule> for ModuleInfo<OsModule> {
289    fn from(val: OsModule) -> Self {
290        ModuleInfo {
291            name: "os",
292            module: val,
293        }
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use crate::test::{call_test, test_async_with, ModuleEvaluator};
300    use rquickjs::{Ctx, Value};
301
302    use super::*;
303
304    async fn run_test_return_string(
305        ctx: &Ctx<'_>,
306        name: &str,
307        is_function: bool,
308        expected_assertion: impl Fn(String),
309    ) {
310        ModuleEvaluator::eval_rust::<OsModule>(ctx.clone(), "os")
311            .await
312            .unwrap();
313
314        let brackets = if is_function { "()" } else { "" };
315        let module = ModuleEvaluator::eval_js(
316            ctx.clone(),
317            "test",
318            &format!(
319                r#"
320                    import {{ {} }} from 'os';
321
322                    export async function test() {{
323                        return {}{}
324                    }}
325                "#,
326                name, name, brackets
327            ),
328        )
329        .await
330        .unwrap();
331
332        let result = call_test::<String, _>(ctx, &module, ()).await;
333        expected_assertion(result);
334    }
335
336    async fn run_test_return_number(ctx: &Ctx<'_>, name: &str, expected_assertion: impl Fn(Value)) {
337        ModuleEvaluator::eval_rust::<OsModule>(ctx.clone(), "os")
338            .await
339            .unwrap();
340
341        let module = ModuleEvaluator::eval_js(
342            ctx.clone(),
343            "test",
344            &format!(
345                r#"
346                    import {{ {} }} from 'os';
347
348                    export async function test() {{
349                        return {}()
350                    }}
351                "#,
352                name, name
353            ),
354        )
355        .await
356        .unwrap();
357
358        let result = call_test::<Value, _>(ctx, &module, ()).await;
359        expected_assertion(result);
360    }
361
362    #[tokio::test]
363    async fn test_available_parallelism() {
364        test_async_with(|ctx| {
365            Box::pin(async move {
366                run_test_return_number(&ctx, "availableParallelism", |result| {
367                    assert!(result.is_number()); // platform dependant
368                })
369                .await;
370            })
371        })
372        .await;
373    }
374
375    #[tokio::test]
376    async fn test_arch() {
377        test_async_with(|ctx| {
378            Box::pin(async move {
379                run_test_return_string(&ctx, "type", true, |result| {
380                    assert!(!result.is_empty()); // platform dependant
381                })
382                .await;
383            })
384        })
385        .await;
386    }
387
388    #[tokio::test]
389    async fn test_devnull() {
390        test_async_with(|ctx| {
391            Box::pin(async move {
392                run_test_return_string(&ctx, "devNull", false, |result| {
393                    assert_eq!(result, DEV_NULL);
394                })
395                .await;
396            })
397        })
398        .await;
399    }
400
401    #[tokio::test]
402    async fn test_endianness() {
403        test_async_with(|ctx| {
404            Box::pin(async move {
405                run_test_return_string(&ctx, "endianness", true, |result| {
406                    let endianness = if cfg!(target_endian = "little") {
407                        "LE".to_string()
408                    } else {
409                        "BE".to_string()
410                    };
411                    assert_eq!(result, endianness);
412                })
413                .await;
414            })
415        })
416        .await;
417    }
418
419    #[tokio::test]
420    async fn test_eol() {
421        test_async_with(|ctx| {
422            Box::pin(async move {
423                run_test_return_string(&ctx, "EOL", false, |result| {
424                    assert_eq!(result, EOL);
425                })
426                .await;
427            })
428        })
429        .await;
430    }
431
432    #[cfg(feature = "statistics")]
433    #[tokio::test]
434    async fn test_freemem() {
435        test_async_with(|ctx| {
436            Box::pin(async move {
437                run_test_return_number(&ctx, "freemem", |result| {
438                    assert!(result.is_number()); // platform dependant
439                })
440                .await;
441            })
442        })
443        .await;
444    }
445
446    #[tokio::test]
447    async fn test_homedir() {
448        test_async_with(|ctx| {
449            Box::pin(async move {
450                run_test_return_string(&ctx, "homedir", true, |result| {
451                    assert!(!result.is_empty()); // platform dependant
452                })
453                .await;
454            })
455        })
456        .await;
457    }
458
459    #[cfg(feature = "system")]
460    #[tokio::test]
461    async fn test_hostname() {
462        test_async_with(|ctx| {
463            Box::pin(async move {
464                run_test_return_string(&ctx, "hostname", true, |result| {
465                    assert!(!result.is_empty()); // platform dependant
466                })
467                .await;
468            })
469        })
470        .await;
471    }
472
473    #[cfg(feature = "system")]
474    #[tokio::test]
475    async fn test_machine() {
476        test_async_with(|ctx| {
477            Box::pin(async move {
478                run_test_return_string(&ctx, "machine", true, |result| {
479                    assert!(!result.is_empty()); // platform dependant
480                })
481                .await;
482            })
483        })
484        .await;
485    }
486
487    #[tokio::test]
488    async fn test_platform() {
489        test_async_with(|ctx| {
490            Box::pin(async move {
491                run_test_return_string(&ctx, "platform", true, |result| {
492                    assert!(!result.is_empty()); // platform dependant
493                })
494                .await;
495            })
496        })
497        .await;
498    }
499
500    #[tokio::test]
501    async fn test_release() {
502        test_async_with(|ctx| {
503            Box::pin(async move {
504                run_test_return_string(&ctx, "release", true, |result| {
505                    assert!(!result.is_empty()); // platform dependant
506                })
507                .await;
508            })
509        })
510        .await;
511    }
512
513    #[tokio::test]
514    async fn test_tmpdir() {
515        test_async_with(|ctx| {
516            Box::pin(async move {
517                run_test_return_string(&ctx, "tmpdir", true, |result| {
518                    assert!(!result.is_empty()); // platform dependant
519                })
520                .await;
521            })
522        })
523        .await;
524    }
525
526    #[cfg(feature = "statistics")]
527    #[tokio::test]
528    async fn test_totalmem() {
529        test_async_with(|ctx| {
530            Box::pin(async move {
531                run_test_return_number(&ctx, "totalmem", |result| {
532                    assert!(result.is_number()); // platform dependant
533                })
534                .await;
535            })
536        })
537        .await;
538    }
539
540    #[tokio::test]
541    async fn test_type() {
542        test_async_with(|ctx| {
543            Box::pin(async move {
544                run_test_return_string(&ctx, "type", true, |result| {
545                    assert!(result == "Linux" || result == "Windows_NT" || result == "Darwin");
546                })
547                .await;
548            })
549        })
550        .await;
551    }
552
553    #[cfg(feature = "system")]
554    #[tokio::test]
555    async fn test_uptime() {
556        test_async_with(|ctx| {
557            Box::pin(async move {
558                run_test_return_number(&ctx, "uptime", |result| {
559                    assert!(result.is_number()); // platform dependant
560                })
561                .await;
562            })
563        })
564        .await;
565    }
566
567    #[tokio::test]
568    async fn test_version() {
569        test_async_with(|ctx| {
570            Box::pin(async move {
571                run_test_return_string(&ctx, "version", true, |result| {
572                    assert!(!result.is_empty()); // platform dependant
573                })
574                .await;
575            })
576        })
577        .await;
578    }
579}