app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
Port occurrence translation and contract authoring to Rust; publish verified R21 source
a06dfd7
26d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
//! Private create-new output. Existing objects are inspected, never repaired.
use serde_json::Value;
use std::{
fs::{self, File, OpenOptions},
io::{Read, Write},
path::Path,
};
#[cfg(any(windows, test))]
mod storage_policy;
#[cfg(windows)]
mod windows_storage;
#[derive(Debug)]
struct Error(String);
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self(e.to_string())
}
}
#[cfg(windows)]
impl Error {
fn at_path(self, stage: &str, path: &Path) -> Self {
Self(format!("{stage} [{}]: {}", path.display(), self.0))
}
}
type Result<T> = std::result::Result<T, Error>;
fn error(code: &str, message: &str) -> Error {
Error(format!("{code}: {message}"))
}
#[cfg(windows)]
fn io_at<T>(result: std::io::Result<T>, stage: &str, path: &Path) -> Result<T> {
result.map_err(|e| Error::from(e).at_path(stage, path))
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct FileIdentity {
volume: u64,
index: u64,
links: u64,
}
fn path_exists(p: &Path) -> Result<bool> {
match fs::symlink_metadata(p) {
Ok(_) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(e.into()),
}
}
fn identity(p: &Path) -> Result<FileIdentity> {
#[cfg(windows)]
{
windows_storage::validate_path(p)?;
let value = windows_storage::identity(p)?;
if value.links != 1 {
return Err(error("linked_file", "exactly one link required"));
}
Ok(value)
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let m = fs::symlink_metadata(p)?;
if !m.is_file() || m.nlink() != 1 || m.mode() & 0o077 != 0 {
return Err(error(
"invalid_file",
"private regular single-link file required",
));
}
Ok(FileIdentity {
volume: m.dev(),
index: m.ino(),
links: m.nlink(),
})
}
}
fn opened_identity(f: &File) -> Result<FileIdentity> {
#[cfg(windows)]
{
windows_storage::inspect(f, false)
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let m = f.metadata()?;
Ok(FileIdentity {
volume: m.dev(),
index: m.ino(),
links: m.nlink(),
})
}
}
fn directory(p: &Path) -> Result<()> {
#[cfg(windows)]
{
windows_storage::validate_path(p)?;
windows_storage::validate_directory(p)
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let m = fs::symlink_metadata(p)?;
if !m.is_dir() || m.file_type().is_symlink() || m.mode() & 0o077 != 0 {
return Err(error(
"invalid_directory",
"private real directory required",
));
}
Ok(())
}
}
fn new_directory(p: &Path) -> Result<()> {
if path_exists(p)? {
return Err(error("existing_directory", "never reuse output directory"));
}
#[cfg(windows)]
{
windows_storage::validate_path(p)?;
windows_storage::create_directory_new(p)?;
}
#[cfg(unix)]
{
use std::os::unix::fs::DirBuilderExt;
fs::DirBuilder::new().mode(0o700).create(p)?;
}
directory(p)
}
fn new_file(p: &Path) -> Result<File> {
#[cfg(windows)]
{
windows_storage::create_file(p)
}
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
Ok(OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.mode(0o600)
.open(p)?)
}
}
fn locked_read(p: &Path) -> Result<File> {
let mut options = OpenOptions::new();
options.read(true);
#[cfg(windows)]
{
use std::os::windows::fs::OpenOptionsExt;
options.share_mode(1).custom_flags(0x00200000);
}
Ok(options.open(p)?)
}
pub fn read_private(p: &Path, max: u64) -> crate::Result<Vec<u8>> {
(function_read(p, max)).map_err(|e| e.0)
}
fn function_read(p: &Path, max: u64) -> Result<Vec<u8>> {
let before = identity(p)?;
let mut f = locked_read(p)?;
if opened_identity(&f)? != before {
return Err(error("file_changed", "opened file identity differs"));
}
let mut data = Vec::new();
(&mut f).take(max + 1).read_to_end(&mut data)?;
if data.len() as u64 > max || identity(p)? != before {
return Err(error(
"bounded_read",
"file exceeded bound or changed identity",
));
}
Ok(data)
}
pub fn immutable_receipt(p: &Path, value: &Value) -> crate::Result<()> {
(function_receipt(p, value)).map_err(|e| e.0)
}
fn function_receipt(p: &Path, value: &Value) -> Result<()> {
directory(p.parent().ok_or_else(|| error("no_parent", "receipt"))?)?;
let bytes = serde_json::to_vec_pretty(value).map_err(|e| Error(e.to_string()))?;
if bytes.len() > 1024 * 1024 {
return Err(error("receipt_size", "one MiB bound"));
}
let mut f = new_file(p)?;
f.write_all(&bytes)?;
f.sync_all()?;
Ok(())
}
pub fn create_output(p: &Path) -> crate::Result<()> {
new_directory(p).map_err(|e| e.0)
}