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