1#![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
134pub fn fill(target: &Object<'_>) -> Result<()> {
141 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
273pub 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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
566 .await;
567 })
568 })
569 .await;
570 }
571}