blob: 2be2b19d9a8af7989390181f2d74a5698fe5de6e [file] [log] [blame]
David Brazdilafc9a9e2023-01-12 16:08:10 +00001// Copyright 2021, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Implementation of the AIDL interface of the VirtualizationService.
16
17use crate::{get_calling_pid, get_calling_uid};
David Brazdil33a31022023-01-12 16:55:16 +000018use crate::atom::{forward_vm_booted_atom, forward_vm_creation_atom, forward_vm_exited_atom};
Alice Wanga410b642023-10-18 09:05:15 +000019use crate::rkpvm::request_attestation;
David Brazdilafc9a9e2023-01-12 16:08:10 +000020use android_os_permissions_aidl::aidl::android::os::IPermissionController;
Alice Wang4e3015d2023-10-10 09:35:37 +000021use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::Certificate::Certificate;
Alice Wangc2fec932023-02-23 16:24:02 +000022use android_system_virtualizationservice::{
Inseob Kim53d0b212023-07-20 16:58:37 +090023 aidl::android::system::virtualizationservice::AssignableDevice::AssignableDevice,
Alice Wangc2fec932023-02-23 16:24:02 +000024 aidl::android::system::virtualizationservice::VirtualMachineDebugInfo::VirtualMachineDebugInfo,
25 binder::ParcelFileDescriptor,
26};
David Brazdilafc9a9e2023-01-12 16:08:10 +000027use android_system_virtualizationservice_internal::aidl::android::system::virtualizationservice_internal::{
28 AtomVmBooted::AtomVmBooted,
29 AtomVmCreationRequested::AtomVmCreationRequested,
30 AtomVmExited::AtomVmExited,
31 IGlobalVmContext::{BnGlobalVmContext, IGlobalVmContext},
Inseob Kim7307a892023-09-14 13:37:58 +090032 IVirtualizationServiceInternal::BoundDevice::BoundDevice,
David Brazdilafc9a9e2023-01-12 16:08:10 +000033 IVirtualizationServiceInternal::IVirtualizationServiceInternal,
Inseob Kimbdca0472023-07-28 19:20:56 +090034 IVfioHandler::{BpVfioHandler, IVfioHandler},
David Brazdilafc9a9e2023-01-12 16:08:10 +000035};
36use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::VM_TOMBSTONES_SERVICE_PORT;
Alice Wangd1b11a02023-04-18 12:30:20 +000037use anyhow::{anyhow, ensure, Context, Result};
Jiyong Parkd7bd2f22023-08-10 20:41:19 +090038use avflog::LogResult;
Jiyong Park2227eaa2023-08-04 11:59:18 +090039use binder::{self, wait_for_interface, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, Status, Strong, IntoBinderResult};
David Brazdilafc9a9e2023-01-12 16:08:10 +000040use libc::VMADDR_CID_HOST;
41use log::{error, info, warn};
42use rustutils::system_properties;
Inseob Kimc4a774d2023-08-30 12:48:43 +090043use serde::Deserialize;
44use std::collections::{HashMap, HashSet};
Seungjae Yoo9d3c20a2023-09-07 15:36:44 +090045use std::fs::{self, create_dir, remove_dir_all, set_permissions, File, Permissions};
David Brazdilafc9a9e2023-01-12 16:08:10 +000046use std::io::{Read, Write};
47use std::os::unix::fs::PermissionsExt;
48use std::os::unix::raw::{pid_t, uid_t};
Inseob Kim55438b22023-08-09 20:16:01 +090049use std::path::{Path, PathBuf};
David Brazdilafc9a9e2023-01-12 16:08:10 +000050use std::sync::{Arc, Mutex, Weak};
51use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
52use vsock::{VsockListener, VsockStream};
Inseob Kimbdca0472023-07-28 19:20:56 +090053use nix::unistd::{chown, Uid};
David Brazdilafc9a9e2023-01-12 16:08:10 +000054
55/// The unique ID of a VM used (together with a port number) for vsock communication.
56pub type Cid = u32;
57
58pub const BINDER_SERVICE_IDENTIFIER: &str = "android.system.virtualizationservice";
59
60/// Directory in which to write disk image files used while running VMs.
61pub const TEMPORARY_DIRECTORY: &str = "/data/misc/virtualizationservice";
62
63/// The first CID to assign to a guest VM managed by the VirtualizationService. CIDs lower than this
64/// are reserved for the host or other usage.
65const GUEST_CID_MIN: Cid = 2048;
66const GUEST_CID_MAX: Cid = 65535;
67
68const SYSPROP_LAST_CID: &str = "virtualizationservice.state.last_cid";
69
70const CHUNK_RECV_MAX_LEN: usize = 1024;
71
72fn is_valid_guest_cid(cid: Cid) -> bool {
73 (GUEST_CID_MIN..=GUEST_CID_MAX).contains(&cid)
74}
75
76/// Singleton service for allocating globally-unique VM resources, such as the CID, and running
77/// singleton servers, like tombstone receiver.
78#[derive(Debug, Default)]
79pub struct VirtualizationServiceInternal {
80 state: Arc<Mutex<GlobalState>>,
81}
82
83impl VirtualizationServiceInternal {
84 pub fn init() -> VirtualizationServiceInternal {
85 let service = VirtualizationServiceInternal::default();
86
87 std::thread::spawn(|| {
88 if let Err(e) = handle_stream_connection_tombstoned() {
89 warn!("Error receiving tombstone from guest or writing them. Error: {:?}", e);
90 }
91 });
92
93 service
94 }
95}
96
97impl Interface for VirtualizationServiceInternal {}
98
99impl IVirtualizationServiceInternal for VirtualizationServiceInternal {
100 fn removeMemlockRlimit(&self) -> binder::Result<()> {
101 let pid = get_calling_pid();
102 let lim = libc::rlimit { rlim_cur: libc::RLIM_INFINITY, rlim_max: libc::RLIM_INFINITY };
103
Andrew Walbranb58d1b42023-07-07 13:54:49 +0100104 // SAFETY: borrowing the new limit struct only
David Brazdilafc9a9e2023-01-12 16:08:10 +0000105 let ret = unsafe { libc::prlimit(pid, libc::RLIMIT_MEMLOCK, &lim, std::ptr::null_mut()) };
106
107 match ret {
108 0 => Ok(()),
Jiyong Park2227eaa2023-08-04 11:59:18 +0900109 -1 => Err(std::io::Error::last_os_error().into()),
110 n => Err(anyhow!("Unexpected return value from prlimit(): {n}")),
David Brazdilafc9a9e2023-01-12 16:08:10 +0000111 }
Jiyong Park2227eaa2023-08-04 11:59:18 +0900112 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
David Brazdilafc9a9e2023-01-12 16:08:10 +0000113 }
114
115 fn allocateGlobalVmContext(
116 &self,
117 requester_debug_pid: i32,
118 ) -> binder::Result<Strong<dyn IGlobalVmContext>> {
119 check_manage_access()?;
120
121 let requester_uid = get_calling_uid();
122 let requester_debug_pid = requester_debug_pid as pid_t;
123 let state = &mut *self.state.lock().unwrap();
Jiyong Park2227eaa2023-08-04 11:59:18 +0900124 state
125 .allocate_vm_context(requester_uid, requester_debug_pid)
126 .or_binder_exception(ExceptionCode::ILLEGAL_STATE)
David Brazdilafc9a9e2023-01-12 16:08:10 +0000127 }
128
129 fn atomVmBooted(&self, atom: &AtomVmBooted) -> Result<(), Status> {
130 forward_vm_booted_atom(atom);
131 Ok(())
132 }
133
134 fn atomVmCreationRequested(&self, atom: &AtomVmCreationRequested) -> Result<(), Status> {
135 forward_vm_creation_atom(atom);
136 Ok(())
137 }
138
139 fn atomVmExited(&self, atom: &AtomVmExited) -> Result<(), Status> {
140 forward_vm_exited_atom(atom);
141 Ok(())
142 }
143
144 fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
145 check_debug_access()?;
146
147 let state = &mut *self.state.lock().unwrap();
148 let cids = state
149 .held_contexts
150 .iter()
151 .filter_map(|(_, inst)| Weak::upgrade(inst))
152 .map(|vm| VirtualMachineDebugInfo {
153 cid: vm.cid as i32,
154 temporaryDirectory: vm.get_temp_dir().to_string_lossy().to_string(),
155 requesterUid: vm.requester_uid as i32,
Charisee96113f32023-01-26 09:00:42 +0000156 requesterPid: vm.requester_debug_pid,
David Brazdilafc9a9e2023-01-12 16:08:10 +0000157 })
158 .collect();
159 Ok(cids)
160 }
Alice Wangc2fec932023-02-23 16:24:02 +0000161
Alice Wang4e3015d2023-10-10 09:35:37 +0000162 fn requestAttestation(&self, csr: &[u8]) -> binder::Result<Vec<Certificate>> {
Alice Wangc2fec932023-02-23 16:24:02 +0000163 check_manage_access()?;
Alice Wanga410b642023-10-18 09:05:15 +0000164 info!("Received csr. Requestting attestation...");
Alice Wange9ac2db2023-09-08 15:13:13 +0000165 if cfg!(remote_attestation) {
Alice Wanga410b642023-10-18 09:05:15 +0000166 request_attestation(csr)
167 .context("Failed to request attestation")
Alice Wange9ac2db2023-09-08 15:13:13 +0000168 .with_log()
169 .or_service_specific_exception(-1)
170 } else {
171 Err(Status::new_exception_str(
172 ExceptionCode::UNSUPPORTED_OPERATION,
173 Some(
Alice Wanga410b642023-10-18 09:05:15 +0000174 "requestAttestation is not supported with the remote_attestation feature \
175 disabled",
Alice Wange9ac2db2023-09-08 15:13:13 +0000176 ),
177 ))
Jiyong Park2227eaa2023-08-04 11:59:18 +0900178 .with_log()
Alice Wange9ac2db2023-09-08 15:13:13 +0000179 }
Alice Wangc2fec932023-02-23 16:24:02 +0000180 }
Inseob Kim53d0b212023-07-20 16:58:37 +0900181
182 fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
183 check_use_custom_virtual_machine()?;
184
Inseob Kim7307a892023-09-14 13:37:58 +0900185 Ok(get_assignable_devices()?
186 .device
187 .into_iter()
188 .map(|x| AssignableDevice { node: x.sysfs_path, kind: x.kind })
189 .collect::<Vec<_>>())
Inseob Kim53d0b212023-07-20 16:58:37 +0900190 }
Inseob Kim1ca0f652023-07-20 17:18:12 +0900191
Inseob Kim7307a892023-09-14 13:37:58 +0900192 fn bindDevicesToVfioDriver(&self, devices: &[String]) -> binder::Result<Vec<BoundDevice>> {
Inseob Kim1ca0f652023-07-20 17:18:12 +0900193 check_use_custom_virtual_machine()?;
194
Inseob Kimbdca0472023-07-28 19:20:56 +0900195 let vfio_service: Strong<dyn IVfioHandler> =
196 wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())?;
Inseob Kimf36347b2023-08-03 12:52:48 +0900197
Seungjae Yoo9d3c20a2023-09-07 15:36:44 +0900198 vfio_service.bindDevicesToVfioDriver(devices)?;
199
200 let dtbo_path = Path::new(TEMPORARY_DIRECTORY).join("common").join("dtbo");
201 if !dtbo_path.exists() {
202 // open a writable file descriptor for vfio_handler
203 let dtbo = File::create(&dtbo_path)
204 .context("Failed to create VM DTBO file")
205 .or_service_specific_exception(-1)?;
206 vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(dtbo))?;
207 }
208
Inseob Kim7307a892023-09-14 13:37:58 +0900209 Ok(get_assignable_devices()?
210 .device
211 .into_iter()
212 .filter_map(|x| {
213 if devices.contains(&x.sysfs_path) {
Jaewan Kim35e818d2023-10-18 05:36:38 +0000214 Some(BoundDevice { sysfsPath: x.sysfs_path, dtboLabel: x.dtbo_label })
Inseob Kim7307a892023-09-14 13:37:58 +0900215 } else {
216 None
217 }
218 })
219 .collect::<Vec<_>>())
Inseob Kim1ca0f652023-07-20 17:18:12 +0900220 }
221}
222
Inseob Kimc4a774d2023-08-30 12:48:43 +0900223// KEEP IN SYNC WITH assignable_devices.xsd
224#[derive(Debug, Deserialize)]
225struct Device {
226 kind: String,
Jaewan Kim35e818d2023-10-18 05:36:38 +0000227 dtbo_label: String,
Inseob Kimc4a774d2023-08-30 12:48:43 +0900228 sysfs_path: String,
229}
230
Inseob Kim7307a892023-09-14 13:37:58 +0900231#[derive(Debug, Default, Deserialize)]
Inseob Kimc4a774d2023-08-30 12:48:43 +0900232struct Devices {
233 device: Vec<Device>,
234}
235
Inseob Kim7307a892023-09-14 13:37:58 +0900236fn get_assignable_devices() -> binder::Result<Devices> {
237 let xml_path = Path::new("/vendor/etc/avf/assignable_devices.xml");
238 if !xml_path.exists() {
239 return Ok(Devices { ..Default::default() });
240 }
241
242 let xml = fs::read(xml_path)
243 .context("Failed to read assignable_devices.xml")
244 .with_log()
245 .or_service_specific_exception(-1)?;
246
247 let xml = String::from_utf8(xml)
248 .context("assignable_devices.xml is not a valid UTF-8 file")
249 .with_log()
250 .or_service_specific_exception(-1)?;
251
252 let mut devices: Devices = serde_xml_rs::from_str(&xml)
253 .context("can't parse assignable_devices.xml")
254 .with_log()
255 .or_service_specific_exception(-1)?;
256
257 let mut device_set = HashSet::new();
258 devices.device.retain(move |device| {
259 if device_set.contains(&device.sysfs_path) {
260 warn!("duplicated assignable device {device:?}; ignoring...");
261 return false;
262 }
263
264 if !Path::new(&device.sysfs_path).exists() {
265 warn!("assignable device {device:?} doesn't exist; ignoring...");
266 return false;
267 }
268
269 device_set.insert(device.sysfs_path.clone());
270 true
271 });
272 Ok(devices)
273}
274
David Brazdilafc9a9e2023-01-12 16:08:10 +0000275#[derive(Debug, Default)]
276struct GlobalVmInstance {
277 /// The unique CID assigned to the VM for vsock communication.
278 cid: Cid,
279 /// UID of the client who requested this VM instance.
280 requester_uid: uid_t,
281 /// PID of the client who requested this VM instance.
282 requester_debug_pid: pid_t,
283}
284
285impl GlobalVmInstance {
286 fn get_temp_dir(&self) -> PathBuf {
287 let cid = self.cid;
288 format!("{TEMPORARY_DIRECTORY}/{cid}").into()
289 }
290}
291
292/// The mutable state of the VirtualizationServiceInternal. There should only be one instance
293/// of this struct.
294#[derive(Debug, Default)]
295struct GlobalState {
296 /// VM contexts currently allocated to running VMs. A CID is never recycled as long
297 /// as there is a strong reference held by a GlobalVmContext.
298 held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
299}
300
301impl GlobalState {
302 /// Get the next available CID, or an error if we have run out. The last CID used is stored in
303 /// a system property so that restart of virtualizationservice doesn't reuse CID while the host
304 /// Android is up.
305 fn get_next_available_cid(&mut self) -> Result<Cid> {
306 // Start trying to find a CID from the last used CID + 1. This ensures
307 // that we do not eagerly recycle CIDs. It makes debugging easier but
308 // also means that retrying to allocate a CID, eg. because it is
309 // erroneously occupied by a process, will not recycle the same CID.
310 let last_cid_prop =
311 system_properties::read(SYSPROP_LAST_CID)?.and_then(|val| match val.parse::<Cid>() {
312 Ok(num) => {
313 if is_valid_guest_cid(num) {
314 Some(num)
315 } else {
316 error!("Invalid value '{}' of property '{}'", num, SYSPROP_LAST_CID);
317 None
318 }
319 }
320 Err(_) => {
321 error!("Invalid value '{}' of property '{}'", val, SYSPROP_LAST_CID);
322 None
323 }
324 });
325
326 let first_cid = if let Some(last_cid) = last_cid_prop {
327 if last_cid == GUEST_CID_MAX {
328 GUEST_CID_MIN
329 } else {
330 last_cid + 1
331 }
332 } else {
333 GUEST_CID_MIN
334 };
335
336 let cid = self
337 .find_available_cid(first_cid..=GUEST_CID_MAX)
338 .or_else(|| self.find_available_cid(GUEST_CID_MIN..first_cid))
339 .ok_or_else(|| anyhow!("Could not find an available CID."))?;
340
341 system_properties::write(SYSPROP_LAST_CID, &format!("{}", cid))?;
342 Ok(cid)
343 }
344
345 fn find_available_cid<I>(&self, mut range: I) -> Option<Cid>
346 where
347 I: Iterator<Item = Cid>,
348 {
349 range.find(|cid| !self.held_contexts.contains_key(cid))
350 }
351
352 fn allocate_vm_context(
353 &mut self,
354 requester_uid: uid_t,
355 requester_debug_pid: pid_t,
356 ) -> Result<Strong<dyn IGlobalVmContext>> {
357 // Garbage collect unused VM contexts.
358 self.held_contexts.retain(|_, instance| instance.strong_count() > 0);
359
360 let cid = self.get_next_available_cid()?;
361 let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
362 create_temporary_directory(&instance.get_temp_dir(), requester_uid)?;
363
364 self.held_contexts.insert(cid, Arc::downgrade(&instance));
365 let binder = GlobalVmContext { instance, ..Default::default() };
366 Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
367 }
368}
369
370fn create_temporary_directory(path: &PathBuf, requester_uid: uid_t) -> Result<()> {
371 if path.as_path().exists() {
372 remove_temporary_dir(path).unwrap_or_else(|e| {
373 warn!("Could not delete temporary directory {:?}: {}", path, e);
374 });
375 }
376 // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
377 // If the chown() fails, this will leave behind an empty directory that will get removed
378 // at the next attempt, or if virtualizationservice is restarted.
379 create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
380 chown(path, Some(Uid::from_raw(requester_uid)), None)
381 .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
382 Ok(())
383}
384
385/// Removes a directory owned by a different user by first changing its owner back
386/// to VirtualizationService.
387pub fn remove_temporary_dir(path: &PathBuf) -> Result<()> {
Alice Wangd1b11a02023-04-18 12:30:20 +0000388 ensure!(path.as_path().is_dir(), "Path {:?} is not a directory", path);
David Brazdilafc9a9e2023-01-12 16:08:10 +0000389 chown(path, Some(Uid::current()), None)?;
390 set_permissions(path, Permissions::from_mode(0o700))?;
Alice Wangd1b11a02023-04-18 12:30:20 +0000391 remove_dir_all(path)?;
David Brazdilafc9a9e2023-01-12 16:08:10 +0000392 Ok(())
393}
394
395/// Implementation of the AIDL `IGlobalVmContext` interface.
396#[derive(Debug, Default)]
397struct GlobalVmContext {
398 /// Strong reference to the context's instance data structure.
399 instance: Arc<GlobalVmInstance>,
400 /// Keeps our service process running as long as this VM context exists.
401 #[allow(dead_code)]
402 lazy_service_guard: LazyServiceGuard,
403}
404
405impl Interface for GlobalVmContext {}
406
407impl IGlobalVmContext for GlobalVmContext {
408 fn getCid(&self) -> binder::Result<i32> {
409 Ok(self.instance.cid as i32)
410 }
411
412 fn getTemporaryDirectory(&self) -> binder::Result<String> {
413 Ok(self.instance.get_temp_dir().to_string_lossy().to_string())
414 }
415}
416
417fn handle_stream_connection_tombstoned() -> Result<()> {
418 // Should not listen for tombstones on a guest VM's port.
419 assert!(!is_valid_guest_cid(VM_TOMBSTONES_SERVICE_PORT as Cid));
420 let listener =
421 VsockListener::bind_with_cid_port(VMADDR_CID_HOST, VM_TOMBSTONES_SERVICE_PORT as Cid)?;
422 for incoming_stream in listener.incoming() {
423 let mut incoming_stream = match incoming_stream {
424 Err(e) => {
425 warn!("invalid incoming connection: {:?}", e);
426 continue;
427 }
428 Ok(s) => s,
429 };
430 std::thread::spawn(move || {
431 if let Err(e) = handle_tombstone(&mut incoming_stream) {
432 error!("Failed to write tombstone- {:?}", e);
433 }
434 });
435 }
436 Ok(())
437}
438
439fn handle_tombstone(stream: &mut VsockStream) -> Result<()> {
440 if let Ok(addr) = stream.peer_addr() {
441 info!("Vsock Stream connected to cid={} for tombstones", addr.cid());
442 }
443 let tb_connection =
444 TombstonedConnection::connect(std::process::id() as i32, DebuggerdDumpType::Tombstone)
445 .context("Failed to connect to tombstoned")?;
446 let mut text_output = tb_connection
447 .text_output
448 .as_ref()
449 .ok_or_else(|| anyhow!("Could not get file to write the tombstones on"))?;
450 let mut num_bytes_read = 0;
451 loop {
452 let mut chunk_recv = [0; CHUNK_RECV_MAX_LEN];
453 let n = stream
454 .read(&mut chunk_recv)
455 .context("Failed to read tombstone data from Vsock stream")?;
456 if n == 0 {
457 break;
458 }
459 num_bytes_read += n;
460 text_output.write_all(&chunk_recv[0..n]).context("Failed to write guests tombstones")?;
461 }
462 info!("Received {} bytes from guest & wrote to tombstone file", num_bytes_read);
463 tb_connection.notify_completion()?;
464 Ok(())
465}
466
467/// Checks whether the caller has a specific permission
468fn check_permission(perm: &str) -> binder::Result<()> {
469 let calling_pid = get_calling_pid();
470 let calling_uid = get_calling_uid();
471 // Root can do anything
472 if calling_uid == 0 {
473 return Ok(());
474 }
475 let perm_svc: Strong<dyn IPermissionController::IPermissionController> =
476 binder::get_interface("permission")?;
477 if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
478 Ok(())
479 } else {
Jiyong Park2227eaa2023-08-04 11:59:18 +0900480 Err(anyhow!("does not have the {} permission", perm))
481 .or_binder_exception(ExceptionCode::SECURITY)
David Brazdilafc9a9e2023-01-12 16:08:10 +0000482 }
483}
484
485/// Check whether the caller of the current Binder method is allowed to call debug methods.
486fn check_debug_access() -> binder::Result<()> {
487 check_permission("android.permission.DEBUG_VIRTUAL_MACHINE")
488}
489
490/// Check whether the caller of the current Binder method is allowed to manage VMs
491fn check_manage_access() -> binder::Result<()> {
492 check_permission("android.permission.MANAGE_VIRTUAL_MACHINE")
493}
Inseob Kim53d0b212023-07-20 16:58:37 +0900494
495/// Check whether the caller of the current Binder method is allowed to use custom VMs
496fn check_use_custom_virtual_machine() -> binder::Result<()> {
497 check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
498}