Skip to content

Commit 21a9fbd

Browse files
committed
fix: remove groups table references from database schema
Remove all references to the non-existent groups table that was causing "no such table: main.groups" errors. This includes: - Removed group_id and group_ip columns from sandboxes table - Removed foreign key constraint to groups table - Updated database functions to remove group parameters - Updated Sandbox model to remove group fields BREAKING CHANGE: Existing databases will need to be recreated or manually updated to remove group columns from the sandboxes table.
1 parent 012e2d4 commit 21a9fbd

File tree

7 files changed

+227
-51
lines changed

7 files changed

+227
-51
lines changed

microsandbox-core/lib/management/db.rs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,6 @@ pub(crate) async fn save_or_update_sandbox(
115115
supervisor_pid: u32,
116116
microvm_pid: u32,
117117
rootfs_paths: &str,
118-
group_id: Option<u32>,
119-
group_ip: Option<String>,
120118
) -> MicrosandboxResult<i64> {
121119
let sandbox = Sandbox {
122120
id: 0,
@@ -127,8 +125,6 @@ pub(crate) async fn save_or_update_sandbox(
127125
supervisor_pid,
128126
microvm_pid,
129127
rootfs_paths: rootfs_paths.to_string(),
130-
group_id,
131-
group_ip,
132128
created_at: Utc::now(),
133129
modified_at: Utc::now(),
134130
};
@@ -142,8 +138,6 @@ pub(crate) async fn save_or_update_sandbox(
142138
supervisor_pid = ?,
143139
microvm_pid = ?,
144140
rootfs_paths = ?,
145-
group_id = ?,
146-
group_ip = ?,
147141
modified_at = CURRENT_TIMESTAMP
148142
WHERE name = ? AND config_file = ?
149143
RETURNING id
@@ -154,8 +148,6 @@ pub(crate) async fn save_or_update_sandbox(
154148
.bind(&sandbox.supervisor_pid)
155149
.bind(&sandbox.microvm_pid)
156150
.bind(&sandbox.rootfs_paths)
157-
.bind(&sandbox.group_id)
158-
.bind(&sandbox.group_ip)
159151
.bind(&sandbox.name)
160152
.bind(&sandbox.config_file)
161153
.fetch_optional(pool)
@@ -171,10 +163,9 @@ pub(crate) async fn save_or_update_sandbox(
171163
r#"
172164
INSERT INTO sandboxes (
173165
name, config_file, config_last_modified,
174-
status, supervisor_pid, microvm_pid, rootfs_paths,
175-
group_id, group_ip
166+
status, supervisor_pid, microvm_pid, rootfs_paths
176167
)
177-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
168+
VALUES (?, ?, ?, ?, ?, ?, ?)
178169
RETURNING id
179170
"#,
180171
)
@@ -185,8 +176,6 @@ pub(crate) async fn save_or_update_sandbox(
185176
.bind(sandbox.supervisor_pid)
186177
.bind(sandbox.microvm_pid)
187178
.bind(sandbox.rootfs_paths)
188-
.bind(sandbox.group_id)
189-
.bind(sandbox.group_ip)
190179
.fetch_one(pool)
191180
.await?;
192181

@@ -203,7 +192,7 @@ pub(crate) async fn get_sandbox(
203192
r#"
204193
SELECT id, name, config_file, config_last_modified, status,
205194
supervisor_pid, microvm_pid, rootfs_paths,
206-
group_id, group_ip, created_at, modified_at
195+
created_at, modified_at
207196
FROM sandboxes
208197
WHERE name = ? AND config_file = ?
209198
"#,
@@ -225,8 +214,6 @@ pub(crate) async fn get_sandbox(
225214
supervisor_pid: row.get("supervisor_pid"),
226215
microvm_pid: row.get("microvm_pid"),
227216
rootfs_paths: row.get("rootfs_paths"),
228-
group_id: row.get("group_id"),
229-
group_ip: row.get("group_ip"),
230217
created_at: parse_sqlite_datetime(&row.get::<String, _>("created_at")),
231218
modified_at: parse_sqlite_datetime(&row.get::<String, _>("modified_at")),
232219
}))
@@ -265,7 +252,7 @@ pub(crate) async fn get_running_config_sandboxes(
265252
r#"
266253
SELECT id, name, config_file, config_last_modified, status,
267254
supervisor_pid, microvm_pid, rootfs_paths,
268-
group_id, group_ip, created_at, modified_at
255+
created_at, modified_at
269256
FROM sandboxes
270257
WHERE config_file = ? AND status = ?
271258
ORDER BY created_at DESC
@@ -290,8 +277,6 @@ pub(crate) async fn get_running_config_sandboxes(
290277
supervisor_pid: row.get("supervisor_pid"),
291278
microvm_pid: row.get("microvm_pid"),
292279
rootfs_paths: row.get("rootfs_paths"),
293-
group_id: row.get("group_id"),
294-
group_ip: row.get("group_ip"),
295280
created_at: parse_sqlite_datetime(&row.get::<String, _>("created_at")),
296281
modified_at: parse_sqlite_datetime(&row.get::<String, _>("modified_at")),
297282
})

microsandbox-core/lib/management/image.rs

Lines changed: 220 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ use microsandbox_utils::term::{self, MULTI_PROGRESS};
1919
use microsandbox_utils::{env, EXTRACTED_LAYER_SUFFIX, LAYERS_SUBDIR, OCI_DB_FILENAME};
2020
use sqlx::{Pool, Sqlite};
2121
#[cfg(feature = "cli")]
22-
use std::io::{Read, Result as IoResult};
22+
use std::io::Result as IoResult;
2323
use std::path::{Path, PathBuf};
24-
#[cfg(feature = "cli")]
24+
use std::ffi::CStr;
25+
use std::io::Read;
2526
use tar::Archive;
2627
use tempfile::tempdir;
2728
use tokio::fs;
@@ -99,16 +100,9 @@ const EXTRACT_LAYERS_MSG: &str = "Extracting layers";
99100
/// ```
100101
pub async fn pull(
101102
name: Reference,
102-
image: bool,
103+
_image: bool,
103104
layer_path: Option<PathBuf>,
104105
) -> MicrosandboxResult<()> {
105-
// Image must be true
106-
if !image {
107-
return Err(MicrosandboxError::InvalidArgument(
108-
"image must be true".to_string(),
109-
));
110-
}
111-
112106
// Single image pull mode (default if both flags are false, or if image is true)
113107
let registry = name.to_string().split('/').next().unwrap_or("").to_string();
114108
let temp_download_dir = tempdir()?.into_path();
@@ -367,10 +361,223 @@ async fn check_image_layers(
367361
}
368362
}
369363

364+
/// Helper function to set xattr with stat information
365+
fn set_stat_xattr(
366+
path: &Path,
367+
xattr_name: &CStr,
368+
uid: u64,
369+
gid: u64,
370+
mode: u32,
371+
) -> Result<(), MicrosandboxError> {
372+
use std::ffi::CString;
373+
374+
let stat_data = format!("{}:{}:0{:o}", uid, gid, mode);
375+
let path_cstring = CString::new(path.as_os_str().as_encoded_bytes())
376+
.map_err(|e| MicrosandboxError::LayerExtraction(format!("Invalid path: {:?}", e)))?;
377+
378+
let result = unsafe {
379+
libc::setxattr(
380+
path_cstring.as_ptr(),
381+
xattr_name.as_ptr(),
382+
stat_data.as_ptr() as *const libc::c_void,
383+
stat_data.len(),
384+
0,
385+
)
386+
};
387+
388+
if result != 0 {
389+
let errno = std::io::Error::last_os_error();
390+
if errno.raw_os_error() == Some(libc::ENOTSUP) {
391+
tracing::warn!(
392+
"Filesystem does not support xattrs for {}, continuing without stat shadowing",
393+
path.display()
394+
);
395+
} else {
396+
return Err(MicrosandboxError::LayerExtraction(format!(
397+
"Failed to set xattr on {}: {}",
398+
path.display(),
399+
errno
400+
)));
401+
}
402+
}
403+
Ok(())
404+
}
405+
370406
/// Extracts a layer from the downloaded tar.gz file into an extracted directory.
371407
/// The extracted directory will be named as <layer-name>.extracted
408+
/// Custom extraction function that modifies file ownership during extraction
409+
fn extract_tar_with_ownership_override<R: Read>(
410+
archive: &mut Archive<R>,
411+
extract_dir: &Path,
412+
) -> MicrosandboxResult<()> {
413+
use std::ffi::CString;
414+
use std::os::unix::fs::PermissionsExt;
415+
416+
// Cache the xattr name to avoid repeated allocations
417+
let xattr_name = CString::new("user.containers.override_stat")
418+
.map_err(|e| MicrosandboxError::LayerExtraction(format!("Invalid attr name: {:?}", e)))?;
419+
420+
// Structure to store hard link information
421+
struct HardLinkInfo {
422+
link_path: PathBuf,
423+
target_path: PathBuf,
424+
uid: u64,
425+
gid: u64,
426+
mode: u32,
427+
}
428+
429+
// Store hard links to process after all regular files are extracted
430+
let mut hard_links = Vec::new();
431+
432+
for entry in archive.entries()? {
433+
let mut entry =
434+
entry.map_err(|e| MicrosandboxError::LayerExtraction(format!("{:?}", e)))?;
435+
let path = entry
436+
.path()
437+
.map_err(|e| MicrosandboxError::LayerExtraction(format!("{:?}", e)))?;
438+
let full_path = extract_dir.join(&path);
439+
440+
// Get the original metadata from the tar entry
441+
let original_uid = entry.header().uid()?;
442+
let original_gid = entry.header().gid()?;
443+
let original_mode = entry.header().mode()?;
444+
445+
// Check the entry type
446+
let entry_type = entry.header().entry_type();
447+
let is_symlink = entry_type.is_symlink();
448+
let is_hard_link = entry_type.is_hard_link();
449+
450+
// Handle hard links separately - collect them for processing after all files are extracted
451+
if is_hard_link {
452+
if let Ok(Some(link_name)) = entry.link_name() {
453+
hard_links.push(HardLinkInfo {
454+
link_path: full_path.clone(),
455+
target_path: extract_dir.join(&link_name),
456+
uid: original_uid,
457+
gid: original_gid,
458+
mode: original_mode,
459+
});
460+
}
461+
continue; // Skip to next entry
462+
}
463+
464+
// Extract the entry (regular files, directories, symlinks)
465+
entry
466+
.unpack(&full_path)
467+
.map_err(|e| MicrosandboxError::LayerExtraction(format!("{:?}", e)))?;
468+
469+
// Skip all operations for symlinks
470+
if is_symlink {
471+
tracing::trace!(
472+
"Extracted symlink {} with original uid:gid:mode {}:{}:{:o}",
473+
full_path.display(),
474+
original_uid,
475+
original_gid,
476+
original_mode
477+
);
478+
continue;
479+
}
480+
481+
// For regular files and directories, handle permissions and xattrs
482+
let metadata = std::fs::metadata(&full_path)?;
483+
let is_dir = metadata.is_dir();
484+
let current_mode = metadata.permissions().mode();
485+
486+
// Calculate the final desired permissions
487+
let desired_mode = if is_dir {
488+
// For directories, ensure at least u+rwx (0o700)
489+
current_mode | 0o700
490+
} else {
491+
// For files, ensure at least u+rw (0o600)
492+
current_mode | 0o600
493+
};
494+
495+
// If we need to modify permissions, do it once
496+
if current_mode != desired_mode {
497+
let mut permissions = metadata.permissions();
498+
permissions.set_mode(desired_mode);
499+
std::fs::set_permissions(&full_path, permissions)?;
500+
}
501+
502+
// Store original uid/gid/mode in xattrs
503+
set_stat_xattr(&full_path, &xattr_name, original_uid, original_gid, original_mode)?;
504+
505+
tracing::trace!(
506+
"Extracted {} with original uid:gid:mode {}:{}:{:o}, stored in xattr",
507+
full_path.display(),
508+
original_uid,
509+
original_gid,
510+
original_mode
511+
);
512+
}
513+
514+
// Second pass: process hard links after all regular files are extracted
515+
for link_info in hard_links {
516+
// Create the hard link
517+
match std::fs::hard_link(&link_info.target_path, &link_info.link_path) {
518+
Ok(_) => {
519+
// Hard link created successfully, now handle xattrs
520+
// Get metadata and ensure proper permissions
521+
let metadata = match std::fs::metadata(&link_info.link_path) {
522+
Ok(m) => m,
523+
Err(e) => {
524+
tracing::warn!(
525+
"Failed to get metadata for hard link {}: {}",
526+
link_info.link_path.display(),
527+
e
528+
);
529+
continue;
530+
}
531+
};
532+
533+
let current_mode = metadata.permissions().mode();
534+
let desired_mode = current_mode | 0o600; // Ensure at least u+rw
535+
536+
// Set permissions if needed
537+
if current_mode != desired_mode {
538+
let mut permissions = metadata.permissions();
539+
permissions.set_mode(desired_mode);
540+
if let Err(e) = std::fs::set_permissions(&link_info.link_path, permissions) {
541+
tracing::warn!(
542+
"Failed to set permissions for hard link {}: {}",
543+
link_info.link_path.display(),
544+
e
545+
);
546+
continue;
547+
}
548+
}
549+
550+
// Store original uid/gid/mode in xattrs
551+
if let Err(e) = set_stat_xattr(&link_info.link_path, &xattr_name, link_info.uid, link_info.gid, link_info.mode) {
552+
// For hard links, we just warn on xattr errors instead of failing
553+
tracing::warn!("Failed to set xattr on hard link {}: {}", link_info.link_path.display(), e);
554+
}
555+
556+
tracing::trace!(
557+
"Created hard link {} -> {} with original uid:gid:mode {}:{}:{:o}",
558+
link_info.link_path.display(),
559+
link_info.target_path.display(),
560+
link_info.uid,
561+
link_info.gid,
562+
link_info.mode
563+
);
564+
}
565+
Err(e) => {
566+
tracing::warn!(
567+
"Failed to create hard link {} -> {}: {}",
568+
link_info.link_path.display(),
569+
link_info.target_path.display(),
570+
e
571+
);
572+
}
573+
}
574+
}
575+
576+
Ok(())
577+
}
578+
372579
async fn extract_layer(
373-
layer_path: impl AsRef<std::path::Path>,
580+
layer_path: impl AsRef<Path>,
374581
extract_base_dir: impl AsRef<Path>,
375582
) -> MicrosandboxResult<()> {
376583
let layer_path = layer_path.as_ref();
@@ -468,7 +675,7 @@ async fn extract_layer(
468675
};
469676
let decoder = GzDecoder::new(reader);
470677
let mut archive = Archive::new(decoder);
471-
archive.unpack(&extract_dir_clone)?;
678+
extract_tar_with_ownership_override(&mut archive, &extract_dir_clone)?;
472679
Ok(())
473680
})
474681
.await
@@ -480,7 +687,6 @@ async fn extract_layer(
480687
#[cfg(not(feature = "cli"))]
481688
{
482689
use flate2::read::GzDecoder;
483-
use tar::Archive;
484690

485691
let file =
486692
std::fs::File::open(layer_path).map_err(|e| MicrosandboxError::LayerHandling {
@@ -489,9 +695,7 @@ async fn extract_layer(
489695
})?;
490696
let decoder = GzDecoder::new(file);
491697
let mut archive = Archive::new(decoder);
492-
archive
493-
.unpack(&extract_dir)
494-
.map_err(|e| MicrosandboxError::LayerExtraction(format!("{:?}", e)))?;
698+
extract_tar_with_ownership_override(&mut archive, &extract_dir)?;
495699
}
496700

497701
tracing::info!(

microsandbox-core/lib/migrations/sandbox/20250128014823_create_sandboxes.down.sql

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
-- Drop indexes first
44
DROP INDEX IF EXISTS idx_sandboxes_name;
5-
DROP INDEX IF EXISTS idx_sandboxes_group_id;
65

76
-- Drop sandboxes table
87
DROP TABLE IF EXISTS sandboxes;

0 commit comments

Comments
 (0)