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(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
142pub fn fill(target: &Object<'_>) -> Result<()> {
149 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
281pub 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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
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()); })
574 .await;
575 })
576 })
577 .await;
578 }
579}