<commit_before>//! `sled` is a high-performance embedded database with
//! an API that is similar to a `BTreeMap<[u8], [u8]>`,
//! but with several additional capabilities for
//! assisting creators of stateful systems.
//!
//! It is fully thread-safe, and all operations are
//! atomic. Multiple `Tree`s are supported with the
//! [`Db::open_tree`](struct.Db.html#method.open_tree) method.
//!
//! ACID transactions involving reads and writes to
//! multiple items are supported with the
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! method. Transactions may also operate over
//! multiple `Tree`s (see
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! docs for more info).
//!
//! Users may also subscribe to updates on individual
//! `Tree`s by using the
//! [`Tree::watch_prefix`](struct.Tree.html#method.watch_prefix)
//! method, which returns a blocking `Iterator` over
//! updates to keys that begin with the provided
//! prefix. You may supply an empty prefix to subscribe
//! to everything.
//!
//! [Merge operators](https://github.com/spacejam/sled/wiki/merge-operators)
//! (aka read-modify-write operators) are supported. A
//! merge operator is a function that specifies
//! how new data can be merged into an existing value
//! without requiring both a read and a write.
//! Using the
//! [`Tree::merge`](struct.Tree.html#method.merge)
//! method, you may "push" data to a `Tree` value
//! and have the provided merge operator combine
//! it with the existing value, if there was one.
//! They are set on a per-`Tree` basis, and essentially
//! allow any sort of data structure to be built
//! using merges as an atomic high-level operation.
//!
//! `sled` is built by experienced database engineers
//! who think users should spend less time tuning and
//! working against high-friction APIs. Expect
//! significant ergonomic and performance improvements
//! over time. Most surprises are bugs, so please
//! [let us know](mailto:t@jujit.su?subject=sled%20sucks!!!) if something
//! is high friction.
//!
//! # Examples
//!
//! ```
//! let t = sled::open("my_db").unwrap();
//!
//! // insert and get
//! t.insert(b"yo!", b"v1");
//! assert_eq!(&t.get(b"yo!").unwrap().unwrap(), b"v1");
//!
//! // Atomic compare-and-swap.
//! t.compare_and_swap(
//!     b"yo!",      // key
//!     Some(b"v1"), // old value, None for not present
//!     Some(b"v2"), // new value, None for delete
//! )
//! .unwrap();
//!
//! // Iterates over key-value pairs, starting at the given key.
//! let scan_key: &[u8] = b"a non-present key before yo!";
//! let mut iter = t.range(scan_key..);
//! assert_eq!(&iter.next().unwrap().unwrap().0, b"yo!");
//! assert_eq!(iter.next(), None);
//!
//! t.remove(b"yo!");
//! assert_eq!(t.get(b"yo!"), Ok(None));
//! # let _ = std::fs::remove_dir_all("my_db");
//! ```
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/spacejam/sled/master/art/tree_face_anti-transphobia.png"
)]
#![cfg_attr(test, deny(warnings))]
#![deny(
    missing_docs,
    future_incompatible,
    nonstandard_style,
    rust_2018_idioms,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unused_qualifications
)]
#![deny(
    // over time, consider enabling the following commented-out lints:
    // clippy::missing_const_for_fn,
    // clippy::missing_docs_in_private_items,
    // clippy::module_name_repetitions,
    // clippy::multiple_crate_versions,
    // clippy::unimplemented,
    // clippy::wildcard_enum_match_arm,
    // clippy::else_if_without_else,
    // clippy::float_arithmetic,
    // clippy::indexing_slicing,
    clippy::cast_lossless,
    clippy::cast_possible_truncation,
    clippy::cast_possible_wrap,
    clippy::cast_precision_loss,
    clippy::cast_sign_loss,
    clippy::checked_conversions,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_enum,
    clippy::expl_impl_clone_on_copy,
    clippy::explicit_into_iter_loop,
    clippy::explicit_iter_loop,
    clippy::fallible_impl_from,
    clippy::filter_map,
    clippy::filter_map_next,
    clippy::find_map,
    clippy::get_unwrap,
    clippy::if_not_else,
    clippy::inline_always,
    clippy::invalid_upcast_comparisons,
    clippy::items_after_statements,
    clippy::map_flatten,
    clippy::match_same_arms,
    clippy::maybe_infinite_iter,
    clippy::mem_forget,
    clippy::multiple_inherent_impl,
    clippy::mut_mut,
    clippy::needless_borrow,
    clippy::needless_continue,
    clippy::needless_pass_by_value,
    clippy::non_ascii_literal,
    clippy::option_map_unwrap_or,
    clippy::option_map_unwrap_or_else,
    clippy::path_buf_push_overwrite,
    clippy::print_stdout,
    clippy::pub_enum_variant_names,
    clippy::redundant_closure_for_method_calls,
    clippy::replace_consts,
    clippy::result_map_unwrap_or_else,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::string_add,
    clippy::string_add_assign,
    clippy::type_repetition_in_bounds,
    clippy::unicode_not_nfc,
    clippy::unseparated_literal_suffix,
    clippy::used_underscore_binding,
    clippy::wildcard_dependencies,
    clippy::wrong_pub_self_convention,
)]
#![recursion_limit = "128"]

#[cfg(feature = "failpoints")]
use fail::fail_point;

macro_rules! maybe_fail {
    ($e:expr) => {
        #[cfg(feature = "failpoints")]
        fail_point!($e, |_| Err(Error::FailPoint));
    };
}

mod batch;
mod binary_search;
mod config;
mod context;
mod db;
mod dll;
mod fastlock;
mod histogram;
mod iter;
mod ivec;
mod lazy;
mod lru;
mod meta;
mod metrics;
mod node;
mod oneshot;
mod pagecache;
mod prefix;
mod result;
mod serialization;
mod stack;
mod subscription;
mod sys_limits;
mod transaction;
mod tree;
mod vecset;

#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
mod threadpool {
    use super::OneShot;

    /// Just execute a task without involving threads.
    pub fn spawn<F, R>(work: F) -> OneShot<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (promise_filler, promise) = OneShot::pair();
        promise_filler.fill((work)());
        return promise;
    }
}

#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
mod threadpool;

#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
mod flusher;

#[cfg(feature = "event_log")]
/// The event log helps debug concurrency issues.
pub mod event_log;

#[cfg(feature = "measure_allocs")]
mod measure_allocs;

#[cfg(feature = "measure_allocs")]
#[global_allocator]
static ALLOCATOR: measure_allocs::TrackingAllocator =
    measure_allocs::TrackingAllocator;

const DEFAULT_TREE_ID: &[u8] = b"__sled__default";

/// hidden re-export of items for testing purposes
#[doc(hidden)]
pub use {
    self::{
        config::RunningConfig,
        lazy::Lazy,
        pagecache::{
            constants::{
                MAX_MSG_HEADER_LEN, MAX_SPACE_AMPLIFICATION,
                MINIMUM_ITEMS_PER_SEGMENT, SEG_HEADER_LEN,
            },
            BatchManifest, DiskPtr, Log, LogKind, LogOffset, LogRead, Lsn,
            PageCache, PageId, SegmentMode,
        },
        serialization::Serialize,
    },
    crossbeam_epoch::{pin, Atomic, Guard, Owned, Shared},
};

pub use self::{
    batch::Batch,
    config::Config,
    db::{open, Db},
    iter::Iter,
    ivec::IVec,
    result::{Error, Result},
    subscription::{Event, Subscriber},
    transaction::{
        abort, ConflictableTransactionError, ConflictableTransactionResult,
        TransactionError, TransactionResult, Transactional, TransactionalTree,
    },
    tree::{CompareAndSwapError, CompareAndSwapResult, Tree},
};

use {
    self::{
        binary_search::binary_search_lub,
        context::Context,
        histogram::Histogram,
        lru::Lru,
        meta::Meta,
        metrics::{clock, Measure, M},
        node::{Data, Node},
        oneshot::{OneShot, OneShotFiller},
        result::CasResult,
        subscription::Subscriptions,
        tree::TreeInner,
        vecset::VecSet,
    },
    crossbeam_utils::{Backoff, CachePadded},
    log::{debug, error, trace, warn},
    pagecache::RecoveryGuard,
    parking_lot::{Condvar, Mutex, RwLock},
    std::{
        collections::BTreeMap,
        convert::TryFrom,
        fmt::{self, Debug},
        io::{Read, Write},
        sync::{
            atomic::{
                AtomicI64 as AtomicLsn, AtomicU64, AtomicUsize,
                Ordering::{Acquire, Relaxed, Release, SeqCst},
            },
            Arc,
        },
    },
};

fn crc32(buf: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(buf);
    hasher.finalize()
}

fn calculate_message_crc32(header: &[u8], body: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(body);
    hasher.update(&header[4..]);
    let crc32 = hasher.finalize();
    crc32 ^ 0xFFFF_FFFF
}

#[cfg(any(test, feature = "lock_free_delays"))]
mod debug_delay;

#[cfg(any(test, feature = "lock_free_delays"))]
use debug_delay::debug_delay;

/// This function is useful for inducing random jitter into our atomic
/// operations, shaking out more possible interleavings quickly. It gets
/// fully eliminated by the compiler in non-test code.
#[cfg(not(any(test, feature = "lock_free_delays")))]
const fn debug_delay() {}

/// Link denotes a tree node or its modification fragment such as
/// key addition or removal.
#[derive(Clone, Debug, PartialEq)]
pub enum Link {
    /// A new value is set for a given key
    Set(IVec, IVec),
    /// The associated value is removed for a given key
    Del(IVec),
    /// A child of this Index node is marked as mergable
    ParentMergeIntention(PageId),
    /// The merging child has been completely merged into its left sibling
    ParentMergeConfirm,
    /// A Node is marked for being merged into its left sibling
    ChildMergeCap,
}

/// A fast map that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastMap8<K, V> = std::collections::HashMap<
    K,
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// A fast set that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastSet8<V> = std::collections::HashSet<
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// Allows arbitrary logic to be injected into mere operations of the
/// `PageCache`.
pub type MergeOperator = fn(
    key: &[u8],
    last_value: Option<&[u8]>,
    new_merge: &[u8],
) -> Option<Vec<u8>>;

fn is_sorted<T: PartialOrd>(xs: &[T]) -> bool {
    xs.windows(2).all(|pair| pair[0] <= pair[1])
}
<commit_msg>Remove imports for removed vecset<commit_after>//! `sled` is a high-performance embedded database with
//! an API that is similar to a `BTreeMap<[u8], [u8]>`,
//! but with several additional capabilities for
//! assisting creators of stateful systems.
//!
//! It is fully thread-safe, and all operations are
//! atomic. Multiple `Tree`s are supported with the
//! [`Db::open_tree`](struct.Db.html#method.open_tree) method.
//!
//! ACID transactions involving reads and writes to
//! multiple items are supported with the
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! method. Transactions may also operate over
//! multiple `Tree`s (see
//! [`Tree::transaction`](struct.Tree.html#method.transaction)
//! docs for more info).
//!
//! Users may also subscribe to updates on individual
//! `Tree`s by using the
//! [`Tree::watch_prefix`](struct.Tree.html#method.watch_prefix)
//! method, which returns a blocking `Iterator` over
//! updates to keys that begin with the provided
//! prefix. You may supply an empty prefix to subscribe
//! to everything.
//!
//! [Merge operators](https://github.com/spacejam/sled/wiki/merge-operators)
//! (aka read-modify-write operators) are supported. A
//! merge operator is a function that specifies
//! how new data can be merged into an existing value
//! without requiring both a read and a write.
//! Using the
//! [`Tree::merge`](struct.Tree.html#method.merge)
//! method, you may "push" data to a `Tree` value
//! and have the provided merge operator combine
//! it with the existing value, if there was one.
//! They are set on a per-`Tree` basis, and essentially
//! allow any sort of data structure to be built
//! using merges as an atomic high-level operation.
//!
//! `sled` is built by experienced database engineers
//! who think users should spend less time tuning and
//! working against high-friction APIs. Expect
//! significant ergonomic and performance improvements
//! over time. Most surprises are bugs, so please
//! [let us know](mailto:t@jujit.su?subject=sled%20sucks!!!) if something
//! is high friction.
//!
//! # Examples
//!
//! ```
//! let t = sled::open("my_db").unwrap();
//!
//! // insert and get
//! t.insert(b"yo!", b"v1");
//! assert_eq!(&t.get(b"yo!").unwrap().unwrap(), b"v1");
//!
//! // Atomic compare-and-swap.
//! t.compare_and_swap(
//!     b"yo!",      // key
//!     Some(b"v1"), // old value, None for not present
//!     Some(b"v2"), // new value, None for delete
//! )
//! .unwrap();
//!
//! // Iterates over key-value pairs, starting at the given key.
//! let scan_key: &[u8] = b"a non-present key before yo!";
//! let mut iter = t.range(scan_key..);
//! assert_eq!(&iter.next().unwrap().unwrap().0, b"yo!");
//! assert_eq!(iter.next(), None);
//!
//! t.remove(b"yo!");
//! assert_eq!(t.get(b"yo!"), Ok(None));
//! # let _ = std::fs::remove_dir_all("my_db");
//! ```
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/spacejam/sled/master/art/tree_face_anti-transphobia.png"
)]
#![cfg_attr(test, deny(warnings))]
#![deny(
    missing_docs,
    future_incompatible,
    nonstandard_style,
    rust_2018_idioms,
    missing_copy_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unused_qualifications
)]
#![deny(
    // over time, consider enabling the following commented-out lints:
    // clippy::missing_const_for_fn,
    // clippy::missing_docs_in_private_items,
    // clippy::module_name_repetitions,
    // clippy::multiple_crate_versions,
    // clippy::unimplemented,
    // clippy::wildcard_enum_match_arm,
    // clippy::else_if_without_else,
    // clippy::float_arithmetic,
    // clippy::indexing_slicing,
    clippy::cast_lossless,
    clippy::cast_possible_truncation,
    clippy::cast_possible_wrap,
    clippy::cast_precision_loss,
    clippy::cast_sign_loss,
    clippy::checked_conversions,
    clippy::decimal_literal_representation,
    clippy::doc_markdown,
    clippy::empty_enum,
    clippy::expl_impl_clone_on_copy,
    clippy::explicit_into_iter_loop,
    clippy::explicit_iter_loop,
    clippy::fallible_impl_from,
    clippy::filter_map,
    clippy::filter_map_next,
    clippy::find_map,
    clippy::get_unwrap,
    clippy::if_not_else,
    clippy::inline_always,
    clippy::invalid_upcast_comparisons,
    clippy::items_after_statements,
    clippy::map_flatten,
    clippy::match_same_arms,
    clippy::maybe_infinite_iter,
    clippy::mem_forget,
    clippy::multiple_inherent_impl,
    clippy::mut_mut,
    clippy::needless_borrow,
    clippy::needless_continue,
    clippy::needless_pass_by_value,
    clippy::non_ascii_literal,
    clippy::option_map_unwrap_or,
    clippy::option_map_unwrap_or_else,
    clippy::path_buf_push_overwrite,
    clippy::print_stdout,
    clippy::pub_enum_variant_names,
    clippy::redundant_closure_for_method_calls,
    clippy::replace_consts,
    clippy::result_map_unwrap_or_else,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::single_match_else,
    clippy::string_add,
    clippy::string_add_assign,
    clippy::type_repetition_in_bounds,
    clippy::unicode_not_nfc,
    clippy::unseparated_literal_suffix,
    clippy::used_underscore_binding,
    clippy::wildcard_dependencies,
    clippy::wrong_pub_self_convention,
)]
#![recursion_limit = "128"]

#[cfg(feature = "failpoints")]
use fail::fail_point;

macro_rules! maybe_fail {
    ($e:expr) => {
        #[cfg(feature = "failpoints")]
        fail_point!($e, |_| Err(Error::FailPoint));
    };
}

mod batch;
mod binary_search;
mod config;
mod context;
mod db;
mod dll;
mod fastlock;
mod histogram;
mod iter;
mod ivec;
mod lazy;
mod lru;
mod meta;
mod metrics;
mod node;
mod oneshot;
mod pagecache;
mod prefix;
mod result;
mod serialization;
mod stack;
mod subscription;
mod sys_limits;
mod transaction;
mod tree;

#[cfg(not(any(windows, target_os = "linux", target_os = "macos")))]
mod threadpool {
    use super::OneShot;

    /// Just execute a task without involving threads.
    pub fn spawn<F, R>(work: F) -> OneShot<R>
    where
        F: FnOnce() -> R + Send + 'static,
        R: Send + 'static,
    {
        let (promise_filler, promise) = OneShot::pair();
        promise_filler.fill((work)());
        return promise;
    }
}

#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
mod threadpool;

#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
mod flusher;

#[cfg(feature = "event_log")]
/// The event log helps debug concurrency issues.
pub mod event_log;

#[cfg(feature = "measure_allocs")]
mod measure_allocs;

#[cfg(feature = "measure_allocs")]
#[global_allocator]
static ALLOCATOR: measure_allocs::TrackingAllocator =
    measure_allocs::TrackingAllocator;

const DEFAULT_TREE_ID: &[u8] = b"__sled__default";

/// hidden re-export of items for testing purposes
#[doc(hidden)]
pub use {
    self::{
        config::RunningConfig,
        lazy::Lazy,
        pagecache::{
            constants::{
                MAX_MSG_HEADER_LEN, MAX_SPACE_AMPLIFICATION,
                MINIMUM_ITEMS_PER_SEGMENT, SEG_HEADER_LEN,
            },
            BatchManifest, DiskPtr, Log, LogKind, LogOffset, LogRead, Lsn,
            PageCache, PageId, SegmentMode,
        },
        serialization::Serialize,
    },
    crossbeam_epoch::{pin, Atomic, Guard, Owned, Shared},
};

pub use self::{
    batch::Batch,
    config::Config,
    db::{open, Db},
    iter::Iter,
    ivec::IVec,
    result::{Error, Result},
    subscription::{Event, Subscriber},
    transaction::{
        abort, ConflictableTransactionError, ConflictableTransactionResult,
        TransactionError, TransactionResult, Transactional, TransactionalTree,
    },
    tree::{CompareAndSwapError, CompareAndSwapResult, Tree},
};

use {
    self::{
        binary_search::binary_search_lub,
        context::Context,
        histogram::Histogram,
        lru::Lru,
        meta::Meta,
        metrics::{clock, Measure, M},
        node::{Data, Node},
        oneshot::{OneShot, OneShotFiller},
        result::CasResult,
        subscription::Subscriptions,
        tree::TreeInner,
    },
    crossbeam_utils::{Backoff, CachePadded},
    log::{debug, error, trace, warn},
    pagecache::RecoveryGuard,
    parking_lot::{Condvar, Mutex, RwLock},
    std::{
        collections::BTreeMap,
        convert::TryFrom,
        fmt::{self, Debug},
        io::{Read, Write},
        sync::{
            atomic::{
                AtomicI64 as AtomicLsn, AtomicU64, AtomicUsize,
                Ordering::{Acquire, Relaxed, Release, SeqCst},
            },
            Arc,
        },
    },
};

fn crc32(buf: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(buf);
    hasher.finalize()
}

fn calculate_message_crc32(header: &[u8], body: &[u8]) -> u32 {
    let mut hasher = crc32fast::Hasher::new();
    hasher.update(body);
    hasher.update(&header[4..]);
    let crc32 = hasher.finalize();
    crc32 ^ 0xFFFF_FFFF
}

#[cfg(any(test, feature = "lock_free_delays"))]
mod debug_delay;

#[cfg(any(test, feature = "lock_free_delays"))]
use debug_delay::debug_delay;

/// This function is useful for inducing random jitter into our atomic
/// operations, shaking out more possible interleavings quickly. It gets
/// fully eliminated by the compiler in non-test code.
#[cfg(not(any(test, feature = "lock_free_delays")))]
const fn debug_delay() {}

/// Link denotes a tree node or its modification fragment such as
/// key addition or removal.
#[derive(Clone, Debug, PartialEq)]
pub enum Link {
    /// A new value is set for a given key
    Set(IVec, IVec),
    /// The associated value is removed for a given key
    Del(IVec),
    /// A child of this Index node is marked as mergable
    ParentMergeIntention(PageId),
    /// The merging child has been completely merged into its left sibling
    ParentMergeConfirm,
    /// A Node is marked for being merged into its left sibling
    ChildMergeCap,
}

/// A fast map that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastMap8<K, V> = std::collections::HashMap<
    K,
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// A fast set that is not resistant to collision attacks. Works
/// on 8 bytes at a time.
pub(crate) type FastSet8<V> = std::collections::HashSet<
    V,
    std::hash::BuildHasherDefault<fxhash::FxHasher64>,
>;

/// Allows arbitrary logic to be injected into mere operations of the
/// `PageCache`.
pub type MergeOperator = fn(
    key: &[u8],
    last_value: Option<&[u8]>,
    new_merge: &[u8],
) -> Option<Vec<u8>>;

fn is_sorted<T: PartialOrd>(xs: &[T]) -> bool {
    xs.windows(2).all(|pair| pair[0] <= pair[1])
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! update-ssh-keys library
//!
//! this library is meant to replace github.com/coreos/update-ssh-keys
//! it will provide the functionality for:
//! * listing authorized keys
//! * adding authorized keys
//! * removing an authorized key by name
//! * disabling an authorized key by name
//!
//! the library will take care of the file locking that is expected from users
//! of the `authorized_keys.d` directory.

#[macro_use]
extern crate error_chain;
extern crate fs2;
extern crate openssh_keys;
extern crate users;

pub mod errors {
    error_chain!{
        foreign_links {
            Io(::std::io::Error);
        }
        errors {
            KeysDisabled(name: String) {
                description("keys are disabled")
                display("keys with name '{}' are disabled", name)
            }
            KeysExist(name: String) {
                description("keys already exist")
                display("keys with name '{}' already exist", name)
            }
        }
    }
}

use errors::*;
use users::{User, switch};
use users::os::unix::UserExt;
use openssh_keys::PublicKey;
use std::fs::{File, self};
use fs2::FileExt;
use std::path::{Path, PathBuf};
use std::io::{BufReader, BufRead, Read, Write};
use std::collections::HashMap;

const SSH_DIR: &'static str = ".ssh";
const AUTHORIZED_KEYS_DIR: &'static str = "authorized_keys.d";
const AUTHORIZED_KEYS_FILE: &'static str = "authorized_keys";
const PRESERVED_KEYS_FILE: &'static str = "orig_authorized_keys";
const LOCK_FILE: &'static str = ".authorized_keys.d.lock";
const STAGE_FILE: &'static str = ".authorized_keys.d.stage_file";
const STAGE_DIR: &'static str = ".authorized_keys.d.stage_dir";

fn lock_file(user: &User) -> PathBuf {
    user.home_dir().join(LOCK_FILE)
}

fn ssh_dir(user: &User) -> PathBuf {
    user.home_dir().join(SSH_DIR)
}

fn authorized_keys_dir(user: &User) -> PathBuf {
    ssh_dir(user).join(AUTHORIZED_KEYS_DIR)
}

fn authorized_keys_file(user: &User) -> PathBuf {
    ssh_dir(user).join(AUTHORIZED_KEYS_FILE)
}

fn stage_dir(user: &User) -> PathBuf {
    ssh_dir(user).join(STAGE_DIR)
}

fn stage_file(user: &User) -> PathBuf {
    ssh_dir(user).join(STAGE_FILE)
}

fn switch_user(user: &User) -> Result<switch::SwitchUserGuard> {
    switch::switch_user_group(user.uid(), user.primary_group_id())
        .chain_err(|| format!("failed to switch to user '{}' with uid '{}'", user.name(), user.uid()))
}

#[derive(Debug)]
struct FileLock {
    pub lock: File,
}

impl Drop for FileLock {
    fn drop(&mut self) {
        self.unlock().unwrap();
    }
}

impl FileLock {
    fn new(path: &Path) -> Result<Self> {
        Ok(FileLock {
            lock: File::create(path)
                .chain_err(|| format!("failed to create lock file: {:?}", path))?,
        })
    }

    fn lock(&self) -> Result<()> {
        self.lock.lock_exclusive()
            .chain_err(|| "failed to lock file")
    }

    fn unlock(&self) -> Result<()> {
        self.lock.unlock()
            .chain_err(|| "failed to unlock file")
    }
}

#[derive(Debug)]
pub struct AuthorizedKeys {
    pub file: PathBuf,
    pub folder: PathBuf,
    pub keys: HashMap<String, AuthorizedKeySet>,
    pub user: User,
    lock: FileLock,
}

impl Drop for AuthorizedKeys {
    fn drop(&mut self) {}
}

#[derive(Clone, Debug)]
pub struct AuthorizedKeySet {
    pub filename: String,
    pub disabled: bool,
    pub keys: Vec<PublicKey>,
}

impl AuthorizedKeys {
    /// write writes all authorized_keys.d changes onto disk. it writes the
    /// current state to a staging directory and then moves that staging
    /// directory to the authorized_keys.d path.
    pub fn write(&self) -> Result<()> {
        // switch users
        let _guard = switch_user(&self.user)?;

        // get our staging directory
        let stage_dir = stage_dir(&self.user);
        if stage_dir.exists() {
            fs::remove_dir_all(&stage_dir)
                .chain_err(|| format!("failed to remove staging directory '{:?}'", stage_dir))?;
        }
        fs::create_dir(&stage_dir)
            .chain_err(|| format!("failed to create staging directory '{:?}'", stage_dir))?;

        // write all the keys to the staging directory
        for keyset in self.keys.values() {
            let keyfilename = stage_dir.join(&keyset.filename);
            let mut keyfile = File::create(&keyfilename)
                .chain_err(|| format!("failed to create file '{:?}'", keyfilename))?;
            // if the keyset is disabled, skip it. we still want to have a
            // zero-sized file with it's name though to signal that it's
            // disabled.
            if keyset.disabled {
                continue
            }
            for key in &keyset.keys {
                write!(keyfile, "{}\n", key)
                    .chain_err(|| format!("failed to write to file '{:?}'", keyfilename))?;
            }
        }

        // destroy the old authorized keys directory and move the staging one to
        // that location. rename expects an existing, empty directory
        fs::remove_dir_all(&self.folder)
            .chain_err(|| format!("failed to remove directory '{:?}'", stage_dir))?;
        fs::create_dir(&self.folder)
            .chain_err(|| format!("failed to create directory '{:?}'", stage_dir))?;
        fs::rename(&stage_dir, &self.folder)
            .chain_err(|| format!("failed to move '{:?}' to '{:?}'", stage_dir, self.folder))
    }

    /// sync writes all the keys we have to authorized_keys. it writes the
    /// current state to a staging file and then moves that staging file to the
    /// authorized_keys path
    pub fn sync(&self) -> Result<()> {
        // switch users
        let _guard = switch_user(&self.user)?;

        // get our staging directory
        let stage_filename = stage_file(&self.user);
        let mut stage_file = File::create(&stage_filename)
            .chain_err(|| format!("failed to create or truncate staging file '{:?}'", stage_filename))?;

        // write all the keys to the staging file
        for keyset in self.keys.values() {
            // if the keyset is disabled, skip it
            if keyset.disabled {
                continue
            }
            for key in &keyset.keys {
                write!(stage_file, "{}\n", key)
                    .chain_err(|| format!("failed to write to file '{:?}'", stage_filename))?;
            }
        }

        // destroy the old authorized keys file and move the staging one to that
        // location
        fs::rename(&stage_filename, &self.file)
            .chain_err(|| format!("failed to move '{:?}' to '{:?}'", stage_filename, self.file))
    }

    /// read_all_keys reads all of the authorized keys files in a given
    /// directory. it returns an error if there is a nested directory, if any
    /// file operations fail, or if it can't parse any of the authorized_keys
    /// files
    fn read_all_keys(dir: &Path) -> Result<HashMap<String, AuthorizedKeySet>> {
        let dir_contents = fs::read_dir(&dir)
            .chain_err(|| format!("failed to read from directory {:?}", dir))?;
        let mut keys = HashMap::new();
        for entry in dir_contents {
            let entry = entry.chain_err(|| format!("failed to read entry in directory {:?}", dir))?;
            let path = entry.path();
            if path.is_dir() {
                // if it's a directory, we don't know what to do
                return Err(format!("'{:?}' is a directory", path).into());
            } else {
                let name = path.file_name()
                    .ok_or_else(|| format!("failed to get filename for '{:?}'", path))?
                    .to_str()
                    .ok_or_else(|| format!("failed to convert filename '{:?}' to string", path))?;
                let from = File::open(&path)
                    .chain_err(|| format!("failed to open file {:?}", path))?;
                let keyset = AuthorizedKeys::read_keys(from)?;
                keys.insert(name.to_string(), AuthorizedKeySet {
                    filename: name.to_string(),
                    disabled: keyset.is_empty(),
                    keys: keyset,
                });
            }
        }
        Ok(keys)
    }

    /// read_keys reads a list of public keys from a reader. it returns an error
    /// of it can't read or parse any of the public keys in the list.
    pub fn read_keys<R>(r: R) -> Result<Vec<PublicKey>>
        where R: Read
    {
        let keybuf = BufReader::new(r);
        // authorized_keys files are newline-separated lists of public keys
        let mut keys = vec![];
        for key in keybuf.lines() {
            let key = key.chain_err(|| "failed to read public key")?;
            // skip any empty lines and any comment lines (prefixed with '#')
            if !key.is_empty() && !(key.trim().starts_with('#')) {
                keys.push(PublicKey::parse(&key)
                          .chain_err(|| "failed to parse public key")?);
            }
        }
        Ok(keys)
    }

    /// open creates a new authorized_keys object. if there is an existing
    /// authorized_keys directory on disk it reads all the keys from that. if
    /// there is no directory already and we are told to create it, we add the
    /// existing authorized keys file as an entry, if it exists.
    /// /// before open actually does any of that, it switches it's uid for the span
    /// of the function and then switched back. it also opens a file lock on the
    /// directory that other instances of `update-ssh-keys` will respect. the
    /// file lock will automatically close when this structure goes out of
    /// scope. you can make sure it is unlocked by calling `drop` yourself in
    /// cases where you think the memory may leak (like if you are tossing boxes
    /// around etc).
    ///
    /// open blocks until it can grab the file lock.
    ///
    /// open returns an error if any file operations fail, if it failes to parse
    /// any of the public keys in the existing files, if it failes to change
    /// users, if it failes to grab the lock, or if create is false but the
    /// directory doesn't exist.
    pub fn open(user: User, create: bool) -> Result<Self> {
        // switch users
        let _guard = switch_user(&user)?;
        // make a new file lock and lock it
        let lock = FileLock::new(&lock_file(&user))?;
        lock.lock()?;

        let akd = authorized_keys_dir(&user);

        let keys = if akd.is_dir() {
            // read the existing keysets from the dir
            AuthorizedKeys::read_all_keys(&akd)?
        } else if !akd.exists() && create {
            // read the existing keyset from the file
            let filename = authorized_keys_file(&user);
            if filename.exists() {
                let file = File::open(&filename)
                    .chain_err(|| format!("failed to open authorized keys file: '{:?}'", filename))?;
                let mut keys = HashMap::new();
                keys.insert(PRESERVED_KEYS_FILE.to_string(), AuthorizedKeySet {
                    filename: PRESERVED_KEYS_FILE.to_string(),
                    disabled: false,
                    keys: AuthorizedKeys::read_keys(file)?,
                });
                keys
            } else {
                // if the authorized_keys file doesn't exist, we don't start
                // with any keys
                HashMap::new()
            }
        } else {
            // either the akd doesn't exist and create is false, or it exists
            // and is not a directory
            return Err(format!("'{:?}' doesn't exist or is not a directory", akd).into())
        };

        Ok(AuthorizedKeys {
            file: authorized_keys_file(&user),
            folder: akd,
            user: user,
            keys: keys,
            lock: lock,
        })
    }

    /// get_keys gets the authorized keyset with the provided name
    pub fn get_keys(&self, name: &str) -> Option<&AuthorizedKeySet> {
        self.keys.get(name)
    }

    /// get_all_keys returns the hashmap from name to keyset containing all the
    /// keys we know about
    pub fn get_all_keys(&self) -> &HashMap<String, AuthorizedKeySet> {
        &self.keys
    }

    /// add_keys adds a list of public keys with the provide name. if replace is
    /// true, it will replace existing keys. if force is true, it will replace
    /// disabled keys.
    ///
    /// add_keys returns an error if the key already exists and replace is
    /// false, or if the key is disabled and force is false
    pub fn add_keys(&mut self, name: &str, keys: Vec<PublicKey>, replace: bool, force: bool) -> Result<()> {
        if let Some(keyset) = self.keys.get(name) {
            if keyset.disabled && !force {
                return Err(ErrorKind::KeysDisabled(name.to_string()).into());
            } else if !replace {
                return Err(ErrorKind::KeysExist(name.to_string()).into());
            }
        }
        self.keys.insert(name.to_string(), AuthorizedKeySet {
            filename: name.to_string(),
            disabled: false,
            keys: keys,
        });
        Ok(())
    }

    /// remove_keys removes the keyset with the given name.
    pub fn remove_keys(&mut self, name: &str) {
        self.keys.remove(name);
    }

    /// disable_keys disables keys with the given name. they can't be added
    /// again unless force is set to true when adding the set. disable_keys will
    /// succeed in disabling the key even if the key doesn't currently exist.
    pub fn disable_keys(&mut self, name: &str) {
        if let Some(keyset) = self.keys.get_mut(name) {
            keyset.disabled = true;
            return;
        }
        self.keys.insert(name.to_string(), AuthorizedKeySet {
            filename: name.to_string(),
            disabled: true,
            keys: vec![],
        });
    }
}
<commit_msg>lib: fixed logging in write function<commit_after>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! update-ssh-keys library
//!
//! this library is meant to replace github.com/coreos/update-ssh-keys
//! it will provide the functionality for:
//! * listing authorized keys
//! * adding authorized keys
//! * removing an authorized key by name
//! * disabling an authorized key by name
//!
//! the library will take care of the file locking that is expected from users
//! of the `authorized_keys.d` directory.

#[macro_use]
extern crate error_chain;
extern crate fs2;
extern crate openssh_keys;
extern crate users;

pub mod errors {
    error_chain!{
        foreign_links {
            Io(::std::io::Error);
        }
        errors {
            KeysDisabled(name: String) {
                description("keys are disabled")
                display("keys with name '{}' are disabled", name)
            }
            KeysExist(name: String) {
                description("keys already exist")
                display("keys with name '{}' already exist", name)
            }
        }
    }
}

use errors::*;
use users::{User, switch};
use users::os::unix::UserExt;
use openssh_keys::PublicKey;
use std::fs::{File, self};
use fs2::FileExt;
use std::path::{Path, PathBuf};
use std::io::{BufReader, BufRead, Read, Write};
use std::collections::HashMap;

const SSH_DIR: &'static str = ".ssh";
const AUTHORIZED_KEYS_DIR: &'static str = "authorized_keys.d";
const AUTHORIZED_KEYS_FILE: &'static str = "authorized_keys";
const PRESERVED_KEYS_FILE: &'static str = "orig_authorized_keys";
const LOCK_FILE: &'static str = ".authorized_keys.d.lock";
const STAGE_FILE: &'static str = ".authorized_keys.d.stage_file";
const STAGE_DIR: &'static str = ".authorized_keys.d.stage_dir";

fn lock_file(user: &User) -> PathBuf {
    user.home_dir().join(LOCK_FILE)
}

fn ssh_dir(user: &User) -> PathBuf {
    user.home_dir().join(SSH_DIR)
}

fn authorized_keys_dir(user: &User) -> PathBuf {
    ssh_dir(user).join(AUTHORIZED_KEYS_DIR)
}

fn authorized_keys_file(user: &User) -> PathBuf {
    ssh_dir(user).join(AUTHORIZED_KEYS_FILE)
}

fn stage_dir(user: &User) -> PathBuf {
    ssh_dir(user).join(STAGE_DIR)
}

fn stage_file(user: &User) -> PathBuf {
    ssh_dir(user).join(STAGE_FILE)
}

fn switch_user(user: &User) -> Result<switch::SwitchUserGuard> {
    switch::switch_user_group(user.uid(), user.primary_group_id())
        .chain_err(|| format!("failed to switch to user '{}' with uid '{}'", user.name(), user.uid()))
}

#[derive(Debug)]
struct FileLock {
    pub lock: File,
}

impl Drop for FileLock {
    fn drop(&mut self) {
        self.unlock().unwrap();
    }
}

impl FileLock {
    fn new(path: &Path) -> Result<Self> {
        Ok(FileLock {
            lock: File::create(path)
                .chain_err(|| format!("failed to create lock file: {:?}", path))?,
        })
    }

    fn lock(&self) -> Result<()> {
        self.lock.lock_exclusive()
            .chain_err(|| "failed to lock file")
    }

    fn unlock(&self) -> Result<()> {
        self.lock.unlock()
            .chain_err(|| "failed to unlock file")
    }
}

#[derive(Debug)]
pub struct AuthorizedKeys {
    pub file: PathBuf,
    pub folder: PathBuf,
    pub keys: HashMap<String, AuthorizedKeySet>,
    pub user: User,
    lock: FileLock,
}

impl Drop for AuthorizedKeys {
    fn drop(&mut self) {}
}

#[derive(Clone, Debug)]
pub struct AuthorizedKeySet {
    pub filename: String,
    pub disabled: bool,
    pub keys: Vec<PublicKey>,
}

impl AuthorizedKeys {
    /// write writes all authorized_keys.d changes onto disk. it writes the
    /// current state to a staging directory and then moves that staging
    /// directory to the authorized_keys.d path.
    pub fn write(&self) -> Result<()> {
        // switch users
        let _guard = switch_user(&self.user)?;

        // get our staging directory
        let stage_dir = stage_dir(&self.user);
        if stage_dir.exists() {
            fs::remove_dir_all(&stage_dir)
                .chain_err(|| format!("failed to remove staging directory '{:?}'", stage_dir))?;
        }
        fs::create_dir(&stage_dir)
            .chain_err(|| format!("failed to create staging directory '{:?}'", stage_dir))?;

        // write all the keys to the staging directory
        for keyset in self.keys.values() {
            let keyfilename = stage_dir.join(&keyset.filename);
            let mut keyfile = File::create(&keyfilename)
                .chain_err(|| format!("failed to create file '{:?}'", keyfilename))?;
            // if the keyset is disabled, skip it. we still want to have a
            // zero-sized file with it's name though to signal that it's
            // disabled.
            if keyset.disabled {
                continue
            }
            for key in &keyset.keys {
                write!(keyfile, "{}\n", key)
                    .chain_err(|| format!("failed to write to file '{:?}'", keyfilename))?;
            }
        }

        // destroy the old authorized keys directory and move the staging one to
        // that location. rename expects an existing, empty directory
        fs::remove_dir_all(&self.folder)
            .chain_err(|| format!("failed to remove directory '{:?}'", self.folder))?;
        fs::create_dir(&self.folder)
            .chain_err(|| format!("failed to create directory '{:?}'", self.folder))?;
        fs::rename(&stage_dir, &self.folder)
            .chain_err(|| format!("failed to move '{:?}' to '{:?}'", stage_dir, self.folder))
    }

    /// sync writes all the keys we have to authorized_keys. it writes the
    /// current state to a staging file and then moves that staging file to the
    /// authorized_keys path
    pub fn sync(&self) -> Result<()> {
        // switch users
        let _guard = switch_user(&self.user)?;

        // get our staging directory
        let stage_filename = stage_file(&self.user);
        let mut stage_file = File::create(&stage_filename)
            .chain_err(|| format!("failed to create or truncate staging file '{:?}'", stage_filename))?;

        // write all the keys to the staging file
        for keyset in self.keys.values() {
            // if the keyset is disabled, skip it
            if keyset.disabled {
                continue
            }
            for key in &keyset.keys {
                write!(stage_file, "{}\n", key)
                    .chain_err(|| format!("failed to write to file '{:?}'", stage_filename))?;
            }
        }

        // destroy the old authorized keys file and move the staging one to that
        // location
        fs::rename(&stage_filename, &self.file)
            .chain_err(|| format!("failed to move '{:?}' to '{:?}'", stage_filename, self.file))
    }

    /// read_all_keys reads all of the authorized keys files in a given
    /// directory. it returns an error if there is a nested directory, if any
    /// file operations fail, or if it can't parse any of the authorized_keys
    /// files
    fn read_all_keys(dir: &Path) -> Result<HashMap<String, AuthorizedKeySet>> {
        let dir_contents = fs::read_dir(&dir)
            .chain_err(|| format!("failed to read from directory {:?}", dir))?;
        let mut keys = HashMap::new();
        for entry in dir_contents {
            let entry = entry.chain_err(|| format!("failed to read entry in directory {:?}", dir))?;
            let path = entry.path();
            if path.is_dir() {
                // if it's a directory, we don't know what to do
                return Err(format!("'{:?}' is a directory", path).into());
            } else {
                let name = path.file_name()
                    .ok_or_else(|| format!("failed to get filename for '{:?}'", path))?
                    .to_str()
                    .ok_or_else(|| format!("failed to convert filename '{:?}' to string", path))?;
                let from = File::open(&path)
                    .chain_err(|| format!("failed to open file {:?}", path))?;
                let keyset = AuthorizedKeys::read_keys(from)?;
                keys.insert(name.to_string(), AuthorizedKeySet {
                    filename: name.to_string(),
                    disabled: keyset.is_empty(),
                    keys: keyset,
                });
            }
        }
        Ok(keys)
    }

    /// read_keys reads a list of public keys from a reader. it returns an error
    /// of it can't read or parse any of the public keys in the list.
    pub fn read_keys<R>(r: R) -> Result<Vec<PublicKey>>
        where R: Read
    {
        let keybuf = BufReader::new(r);
        // authorized_keys files are newline-separated lists of public keys
        let mut keys = vec![];
        for key in keybuf.lines() {
            let key = key.chain_err(|| "failed to read public key")?;
            // skip any empty lines and any comment lines (prefixed with '#')
            if !key.is_empty() && !(key.trim().starts_with('#')) {
                keys.push(PublicKey::parse(&key)
                          .chain_err(|| "failed to parse public key")?);
            }
        }
        Ok(keys)
    }

    /// open creates a new authorized_keys object. if there is an existing
    /// authorized_keys directory on disk it reads all the keys from that. if
    /// there is no directory already and we are told to create it, we add the
    /// existing authorized keys file as an entry, if it exists.
    /// /// before open actually does any of that, it switches it's uid for the span
    /// of the function and then switched back. it also opens a file lock on the
    /// directory that other instances of `update-ssh-keys` will respect. the
    /// file lock will automatically close when this structure goes out of
    /// scope. you can make sure it is unlocked by calling `drop` yourself in
    /// cases where you think the memory may leak (like if you are tossing boxes
    /// around etc).
    ///
    /// open blocks until it can grab the file lock.
    ///
    /// open returns an error if any file operations fail, if it failes to parse
    /// any of the public keys in the existing files, if it failes to change
    /// users, if it failes to grab the lock, or if create is false but the
    /// directory doesn't exist.
    pub fn open(user: User, create: bool) -> Result<Self> {
        // switch users
        let _guard = switch_user(&user)?;
        // make a new file lock and lock it
        let lock = FileLock::new(&lock_file(&user))?;
        lock.lock()?;

        let akd = authorized_keys_dir(&user);

        let keys = if akd.is_dir() {
            // read the existing keysets from the dir
            AuthorizedKeys::read_all_keys(&akd)?
        } else if !akd.exists() && create {
            // read the existing keyset from the file
            let filename = authorized_keys_file(&user);
            if filename.exists() {
                let file = File::open(&filename)
                    .chain_err(|| format!("failed to open authorized keys file: '{:?}'", filename))?;
                let mut keys = HashMap::new();
                keys.insert(PRESERVED_KEYS_FILE.to_string(), AuthorizedKeySet {
                    filename: PRESERVED_KEYS_FILE.to_string(),
                    disabled: false,
                    keys: AuthorizedKeys::read_keys(file)?,
                });
                keys
            } else {
                // if the authorized_keys file doesn't exist, we don't start
                // with any keys
                HashMap::new()
            }
        } else {
            // either the akd doesn't exist and create is false, or it exists
            // and is not a directory
            return Err(format!("'{:?}' doesn't exist or is not a directory", akd).into())
        };

        Ok(AuthorizedKeys {
            file: authorized_keys_file(&user),
            folder: akd,
            user: user,
            keys: keys,
            lock: lock,
        })
    }

    /// get_keys gets the authorized keyset with the provided name
    pub fn get_keys(&self, name: &str) -> Option<&AuthorizedKeySet> {
        self.keys.get(name)
    }

    /// get_all_keys returns the hashmap from name to keyset containing all the
    /// keys we know about
    pub fn get_all_keys(&self) -> &HashMap<String, AuthorizedKeySet> {
        &self.keys
    }

    /// add_keys adds a list of public keys with the provide name. if replace is
    /// true, it will replace existing keys. if force is true, it will replace
    /// disabled keys.
    ///
    /// add_keys returns an error if the key already exists and replace is
    /// false, or if the key is disabled and force is false
    pub fn add_keys(&mut self, name: &str, keys: Vec<PublicKey>, replace: bool, force: bool) -> Result<()> {
        if let Some(keyset) = self.keys.get(name) {
            if keyset.disabled && !force {
                return Err(ErrorKind::KeysDisabled(name.to_string()).into());
            } else if !replace {
                return Err(ErrorKind::KeysExist(name.to_string()).into());
            }
        }
        self.keys.insert(name.to_string(), AuthorizedKeySet {
            filename: name.to_string(),
            disabled: false,
            keys: keys,
        });
        Ok(())
    }

    /// remove_keys removes the keyset with the given name.
    pub fn remove_keys(&mut self, name: &str) {
        self.keys.remove(name);
    }

    /// disable_keys disables keys with the given name. they can't be added
    /// again unless force is set to true when adding the set. disable_keys will
    /// succeed in disabling the key even if the key doesn't currently exist.
    pub fn disable_keys(&mut self, name: &str) {
        if let Some(keyset) = self.keys.get_mut(name) {
            keyset.disabled = true;
            return;
        }
        self.keys.insert(name.to_string(), AuthorizedKeySet {
            filename: name.to_string(),
            disabled: true,
            keys: vec![],
        });
    }
}
<|endoftext|>


<commit_before>// module for triangulating a simple polygon using ear clipping

use std::collections::HashSet;

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Point {
    x: f32,
    y: f32
}
impl Point {
    pub fn new(x: f32, y: f32) -> Point {
        Point { x: x, y: y }
    }
}

#[derive(Debug, PartialEq)]
struct Vertex {
    index: usize,
    prev_index: usize,
    next_index: usize,
    is_convex: bool,
    is_ear: bool
}
impl Vertex {
    fn new(index: usize, prev_index: usize, next_index: usize) -> Vertex {
        Vertex { index: index, prev_index: prev_index, next_index: next_index, is_convex: false, is_ear: false }
    }
}

fn compare_to_line(v_test: &Point, v_prev: &Point, v_next: &Point) -> f32 {
    // returns negative if test is left of line, positive if right, 0 if colinear
    (v_test.x - v_prev.x)*(v_next.y - v_prev.y) - (v_test.y - v_prev.y)*(v_next.x - v_prev.x)
}

fn is_convex(v_test: &Point, v_prev: &Point, v_next: &Point) -> bool {
    // point is convex if right of line made by prev->next
    compare_to_line(v_test, v_prev, v_next) > 0.0f32
}

fn is_in_triangle(v_test: &Point, v0: &Point, v1: &Point, v2: &Point) -> bool {
    if compare_to_line(v_test, v0, v1) >= 0.0f32 { return false; }
    if compare_to_line(v_test, v1, v2) >= 0.0f32 { return false; }
    if compare_to_line(v_test, v2, v0) >= 0.0f32 { return false; }
    true
}

// note: this function assumes v_test is convex!
fn is_ear(points: &Vec<Point>, reflex_set: &HashSet<usize>, v_test: &Vertex) -> bool {
    for r in reflex_set {
        if *r == v_test.prev_index || *r == v_test.next_index {
            continue;
        }
        if is_in_triangle(&points[*r],
                          &points[v_test.prev_index],
                          &points[v_test.index],
                          &points[v_test.next_index]) {
            return false;
        }
    }
    true
}

fn make_vertex_vec(n: usize) -> Vec<Vertex> {
    let mut vertices = Vec::with_capacity(n);
    vertices.push(Vertex::new(0, n-1, 1));
    for i in 1..(n-1) {
        vertices.push(Vertex::new(i, i-1, i+1));
    }
    vertices.push(Vertex::new(n-1, n-2, 0));
    vertices
}

fn classify_vertex(points: &Vec<Point>, v_test: &mut Vertex,
        ear_vec: &mut HashSet<usize>, reflex_set: &mut HashSet<usize>) {
    if is_convex(&points[v_test.index], &points[v_test.prev_index], &points[v_test.next_index]) {
        v_test.is_convex = true;
        if is_ear(&points, reflex_set, &v_test) {
            v_test.is_ear = true;
            ear_vec.insert(v_test.index);
        }
    } else {
        reflex_set.insert(v_test.index);
    }
}

fn fill_sets(points: &Vec<Point>, vertices: &mut Vec<Vertex>) -> (HashSet<usize>, HashSet<usize>) {
    let mut ear_set = HashSet::new();
    let mut reflex_set = HashSet::new();

    for v in vertices {
        classify_vertex(points, v, &mut ear_set, &mut reflex_set);
    }

    (ear_set, reflex_set)
}

fn remove_vertex(vertices: &mut Vec<Vertex>, i_test: usize) {
    let prev_index = vertices[i_test].prev_index;
    let next_index = vertices[i_test].next_index;
    vertices[prev_index].next_index = next_index;
    vertices[next_index].prev_index = prev_index;
}

fn push_triangle(triangles: &mut Vec<Point>, points: &Vec<Point>, i_test: usize, i_prev: usize, i_next: usize) {
    triangles.push(points[i_prev]);
    triangles.push(points[i_test]);
    triangles.push(points[i_next]);
}

pub fn triangulate(points: Vec<Point>) -> Result<Vec<Point>, &'static str> {
    let mut n = points.len();
    if n < 4 {
        if n == 3 {
            return Ok(points);
        }  else {
            return Err("Not enough vertices to triangulate")
        }
    }

    let mut vertices = make_vertex_vec(n);
    let (mut ear_set, mut reflex_set) = fill_sets(&points, &mut vertices);

    let mut triangles = Vec::with_capacity(3 * (n - 1));

    loop {
        let ear_index = match ear_set.iter().next() {
            Some(i) => *i,
            None => return Err("Expected an ear in the ear set but found None")
        };
        ear_set.remove(&ear_index);
        let index;
        let prev_index;
        let next_index;
         {
            let vertex = &vertices[ear_index];
            index = vertex.index;
            prev_index = vertex.prev_index;
            next_index = vertex.next_index;
        }
        push_triangle(&mut triangles, &points, ear_index, prev_index, next_index);
        remove_vertex(&mut vertices, index);
        n -= 1;

        if n == 3 {
            let ear_index = match ear_set.iter().next() {
                Some(i) => *i,
                None => return Err("Expected an ear in the ear set but found None")
            };
            let vertex = &vertices[ear_index];
            push_triangle(&mut triangles, &points, ear_index, vertex.index, vertex.next_index);
            return Ok(triangles);
        }

        {
            let ref mut v_prev = vertices[prev_index];
            if v_prev.is_ear {
                if !is_ear(&points, &reflex_set, v_prev) {
                    v_prev.is_ear = false;
                    ear_set.remove(&prev_index);
                }
            } else {
                if is_convex(&points[prev_index], &points[v_prev.prev_index], &points[v_prev.next_index]) {
                    if !v_prev.is_convex {
                        v_prev.is_convex = true;
                        reflex_set.remove(&prev_index);
                    }
                    if is_ear(&points, &reflex_set, v_prev) {
                        ear_set.insert(prev_index);
                    }
                }
            }
        }

        {
            let ref mut v_next = vertices[next_index];
            if v_next.is_ear {
                if !is_ear(&points, &reflex_set, v_next) {
                    v_next.is_ear = false;
                    ear_set.remove(&next_index);
                }
            } else {
                if is_convex(&points[next_index], &points[v_next.prev_index],& points[v_next.next_index]) {
                    if !v_next.is_convex {
                        v_next.is_convex = true;
                        reflex_set.remove(&next_index);
                    }
                    if is_ear(&points, &reflex_set, v_next) {
                        ear_set.insert(next_index);
                    }
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Point;
    use super::compare_to_line;
    use super::is_convex;
    use super::is_in_triangle;

    #[test]
    fn test_compare_to_line() {
        let test = Point::new(0.2f32, 1.2f32);
        let v0 = Point::new(0.1f32,  0.1f32);
        let v1 = Point::new(0.5f32, 0.5f32);

        assert!(compare_to_line(&test, &v0, &v1) < 0.0f32);

        let test = Point::new(0.2f32, -1.0f32);

        assert!(compare_to_line(&test, &v0, &v1) > 0.0f32);

        let test = Point::new(v0.x + (v1.x - v0.x) * 0.2f32, v0.y + (v1.y - v0.y) * 0.2f32);

        assert_eq!(compare_to_line(&test, &v0, &v1), 0.0f32);
    }

    #[test]
    fn test_is_convex() {
        let test = Point::new(0.2f32, 1.2f32);
        let v0 = Point::new(0.1f32, 0.1f32);
        let v1 = Point::new(0.5f32, 0.5f32);

        assert_eq!(is_convex(&test, &v0, &v1), false);

        let test = Point::new(0.2f32, -1.0f32);

        assert!(is_convex(&test, &v0, &v1));

        let test = Point::new(v0.x + (v1.x - v0.x) * 0.2f32, v0.y + (v1.y - v0.y) * 0.2f32);

        assert_eq!(is_convex(&test, &v0, &v1), false);
    }

    #[test]
    fn test_is_in_triangle() {
        let v0 = Point::new(0.1f32, 0.1f32);
        let v1 = Point::new(0.7f32, -0.2f32);
        let v2 = Point::new(0.5f32, 0.5f32);

        let test = Point::new(0.2f32, 1.2f32);
        assert_eq!(is_in_triangle(&test, &v0, &v1, &v2), false);

        let test = Point::new(0.4f32, 0.2f32);
        assert!(is_in_triangle(&test, &v0, &v1, &v2));

        let test = Point::new(v2.x + (v0.x - v2.x) * 0.25f32, v2.y + (v0.y - v2.y) * 0.25f32);
        assert_eq!(is_in_triangle(&test, &v0, &v1, &v2), false);
    }
}<commit_msg>made some fixes and a couple top-level unit tests<commit_after>    // module for triangulating a simple polygon using ear clipping

use std::collections::HashSet;
use std::fmt;

#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Point {
    x: f32,
    y: f32
}
impl Point {
    pub fn new(x: f32, y: f32) -> Point {
        Point { x: x, y: y }
    }
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

#[derive(Debug, PartialEq)]
struct Vertex {
    index: usize,
    prev_index: usize,
    next_index: usize,
    is_convex: bool,
    is_ear: bool
}
impl Vertex {
    fn new(index: usize, prev_index: usize, next_index: usize) -> Vertex {
        Vertex { index: index, prev_index: prev_index, next_index: next_index, is_convex: false, is_ear: false }
    }
}

fn compare_to_line(v_test: &Point, v_prev: &Point, v_next: &Point) -> f32 {
    // returns negative if test is left of line, positive if right, 0 if colinear
    (v_test.x - v_prev.x)*(v_next.y - v_prev.y) - (v_test.y - v_prev.y)*(v_next.x - v_prev.x)
}

fn is_convex(v_test: &Point, v_prev: &Point, v_next: &Point) -> bool {
    // point is convex if right of line made by prev->next
    compare_to_line(v_test, v_prev, v_next) > 0.0f32
}

fn is_in_triangle(v_test: &Point, v0: &Point, v1: &Point, v2: &Point) -> bool {
    if compare_to_line(v_test, v0, v1) >= 0.0f32 { return false; }
    if compare_to_line(v_test, v1, v2) >= 0.0f32 { return false; }
    if compare_to_line(v_test, v2, v0) >= 0.0f32 { return false; }
    true
}

// note: this function assumes v_test is convex!
fn is_ear(points: &Vec<Point>, reflex_set: &HashSet<usize>, v_test: &Vertex) -> bool {
    for r in reflex_set {
        if *r == v_test.prev_index || *r == v_test.next_index {
            continue;
        }
        if is_in_triangle(&points[*r],
                          &points[v_test.prev_index],
                          &points[v_test.index],
                          &points[v_test.next_index]) {
            return false;
        }
    }
    true
}

fn make_vertex_vec(n: usize) -> Vec<Vertex> {
    let mut vertices = Vec::with_capacity(n);
    vertices.push(Vertex::new(0, n-1, 1));
    for i in 1..(n-1) {
        vertices.push(Vertex::new(i, i-1, i+1));
    }
    vertices.push(Vertex::new(n-1, n-2, 0));
    vertices
}

#[derive(Debug, Eq, PartialEq)]
enum VertexType {
    Reflex,
    Convex,
    Ear
}

fn classify_vertex(points: &Vec<Point>, v_test: &mut Vertex, reflex_set: &HashSet<usize>) -> VertexType {
    if is_convex(&points[v_test.index], &points[v_test.prev_index], &points[v_test.next_index]) {
        v_test.is_convex = true;
        if is_ear(&points, reflex_set, &v_test) {
            v_test.is_ear = true;
             return VertexType::Ear;
        } else {
            return VertexType::Convex;
        }
    } else {
        return VertexType::Reflex;
    }
}

fn fill_sets(points: &Vec<Point>, vertices: &mut Vec<Vertex>) -> (HashSet<usize>, HashSet<usize>) {
    let mut ear_set = HashSet::new();
    let mut reflex_set = HashSet::new();

    for v in vertices.iter_mut() {
        if classify_vertex(points, v, &reflex_set) == VertexType::Reflex {
            reflex_set.insert(v.index);
        }
    }

    for mut v in vertices.iter_mut() {
        match classify_vertex(points, v, &reflex_set) {
            VertexType::Reflex => (),
            VertexType::Convex => {
                v.is_convex = true;
            }
            VertexType::Ear => {
                v.is_convex = true;
                v.is_ear = true;
                ear_set.insert(v.index);
            }
        }
    }
    (ear_set, reflex_set)
}

fn remove_vertex(vertices: &mut Vec<Vertex>, i_test: usize) {

    let prev_index = vertices[i_test].prev_index;
    let next_index = vertices[i_test].next_index;
    vertices[prev_index].next_index = next_index;
    vertices[next_index].prev_index = prev_index;
}

fn push_triangle(triangles: &mut Vec<usize>, i_test: usize, i_prev: usize, i_next: usize) {
    triangles.push(i_prev);
    triangles.push(i_test);
    triangles.push(i_next);
}

pub fn triangulate(points: &Vec<Point>) -> Result<Vec<usize>, &'static str> {
    let mut n = points.len();
    if n < 4 {
        if n == 3 {
            return Ok(vec![0, 1, 2]);
        }  else {
            return Err("Not enough vertices to triangulate");
        }
    }

    let mut vertices = make_vertex_vec(n);
    let (mut ear_set, mut reflex_set) = fill_sets(points, &mut vertices);

    let mut triangles = Vec::with_capacity(3 * (n - 2));

    loop {
        let ear_index = match ear_set.iter().next() {
            Some(i) => *i,
            None => return Err("Expected an ear in the ear set but found None")
        };

        ear_set.remove(&ear_index);
        let prev_index;
        let next_index;
        {
            let vertex = &vertices[ear_index];
            prev_index = vertex.prev_index;
            next_index = vertex.next_index;
        }
        push_triangle(&mut triangles, ear_index, prev_index, next_index);
        remove_vertex(&mut vertices, ear_index);
        n -= 1;

        if n == 3 {
            let ear_index = match ear_set.iter().next() {
                Some(i) => *i,
                None => return Err("Expected an ear in the ear set but found None")
            };
            let prev_index;
            let next_index;
            {
                let vertex = &vertices[ear_index];
                prev_index = vertex.prev_index;
                next_index = vertex.next_index;
            }
            push_triangle(&mut triangles, ear_index, prev_index, next_index);
            return Ok(triangles);
        }

        {
            let ref mut v_prev = vertices[prev_index];
            if v_prev.is_ear {
                if !is_ear(&points, &reflex_set, v_prev) {
                    v_prev.is_ear = false;
                    ear_set.remove(&prev_index);
                }
            } else {
                if is_convex(&points[prev_index], &points[v_prev.prev_index], &points[v_prev.next_index]) {
                    if !v_prev.is_convex {
                        v_prev.is_convex = true;
                        reflex_set.remove(&prev_index);
                    }
                    if is_ear(points, &reflex_set, v_prev) {
                        ear_set.insert(prev_index);
                    }
                }
            }
        }

        {
            let ref mut v_next = vertices[next_index];
            if v_next.is_ear {
                if !is_ear(&points, &reflex_set, v_next) {
                    v_next.is_ear = false;
                    ear_set.remove(&next_index);
                }
            } else {
                if is_convex(&points[next_index], &points[v_next.prev_index], &points[v_next.next_index]) {
                    if !v_next.is_convex {
                        v_next.is_convex = true;
                        reflex_set.remove(&next_index);
                    }
                    if is_ear(&points, &reflex_set, v_next) {
                        ear_set.insert(next_index);
                    }
                }
            }
        }
    }
}
#[cfg(test)]
mod tests {
    use super::Point;
    use super::compare_to_line;
    use super::is_convex;
    use super::is_in_triangle;
    use super::triangulate;

    #[test]
    fn test_compare_to_line() {
        let test = Point::new(0.2f32, 1.2f32);
        let v0 = Point::new(0.1f32,  0.1f32);
        let v1 = Point::new(0.5f32, 0.5f32);

        assert!(compare_to_line(&test, &v0, &v1) < 0.0f32);

        let test = Point::new(0.2f32, -1.0f32);

        assert!(compare_to_line(&test, &v0, &v1) > 0.0f32);

        let test = Point::new(v0.x + (v1.x - v0.x) * 0.2f32, v0.y + (v1.y - v0.y) * 0.2f32);

        assert_eq!(compare_to_line(&test, &v0, &v1), 0.0f32);
    }

    #[test]
    fn test_is_convex() {
        let test = Point::new(0.2f32, 1.2f32);
        let v0 = Point::new(0.1f32, 0.1f32);
        let v1 = Point::new(0.5f32, 0.5f32);

        assert_eq!(is_convex(&test, &v0, &v1), false);

        let test = Point::new(0.2f32, -1.0f32);

        assert!(is_convex(&test, &v0, &v1));

        let test = Point::new(v0.x + (v1.x - v0.x) * 0.2f32, v0.y + (v1.y - v0.y) * 0.2f32);

        assert_eq!(is_convex(&test, &v0, &v1), false);
    }

    #[test]
    fn test_is_in_triangle() {
        let v0 = Point::new(0.1f32, 0.1f32);
        let v1 = Point::new(0.7f32, -0.2f32);
        let v2 = Point::new(0.5f32, 0.5f32);

        let test = Point::new(0.2f32, 1.2f32);
        assert_eq!(is_in_triangle(&test, &v0, &v1, &v2), false);

        let test = Point::new(0.4f32, 0.2f32);
        assert!(is_in_triangle(&test, &v0, &v1, &v2));

        let test = Point::new(v2.x + (v0.x - v2.x) * 0.25f32, v2.y + (v0.y - v2.y) * 0.25f32);
        assert_eq!(is_in_triangle(&test, &v0, &v1, &v2), false);
    }

    fn is_expected_triangle(v0: &usize, v1: &usize, v2: &usize, expected: &(usize, usize, usize)) -> bool {
        if *v0 == expected.0 && *v1 == expected.1 && *v2 == expected.2 {
            println!("{}, {}, {} is expected triangle", *v0, *v1, *v2);
            return true;
        }
        if *v1 == expected.0 && *v2 == expected.1 && *v0 == expected.2 {
            println!("{}, {}, {} is expected triangle", *v0, *v1, *v2);
            return true;
        }
        if *v2 == expected.0 && *v0 == expected.1 && *v1 == expected.2 {
            println!("{}, {}, {} is expected triangle", *v0, *v1, *v2);
            return true;
        }
        println!("{}, {}, {}, did not match {}, {}, {}", *v0, *v1, *v2, expected.0, expected.1, expected.2);
        false
    }

    fn is_same_triangulation(triangles: &Vec<usize>, mut expected: Vec<(usize, usize, usize)>) -> bool {
        let n = triangles.len() / 3;
        for i in 0..n {
            let mut matched = false;
            let mut match_index = 0;
            for (index, ex) in expected.iter().enumerate() {
                if is_expected_triangle(&triangles[i*3], &triangles[i*3+1], &triangles[i*3+2], ex) {
                    matched = true;
                    match_index = index;
                    break;
                }
            }
            if matched {
                expected.remove(match_index);
            } else {
                return false;
            }
        }
        true
    }

    #[test]
    fn test_triangulate_square() {
        let points = vec![ Point::new(0.0f32, 0.0f32),
                           Point::new(1.0f32, 0.0f32),
                           Point::new(1.0f32, 1.0f32),
                           Point::new(0.0f32, 1.0f32) ];

        let triangles = triangulate(&points).unwrap();

        for i in 0..(triangles.len() / 3) {
            println!("{}, {}, {}", triangles[i*3], triangles[i*3+1], triangles[i*3+2]);
        }

        // there are two valid triangulations of a square, up-right diagonal
        // and down-right diagonal, so we try both
        assert!(is_same_triangulation(&triangles, vec![(0, 1, 2), (0, 2, 3)]) ||
                is_same_triangulation(&triangles, vec![(0, 1, 3), (3, 1, 2)]));
    }

    #[test]
    fn test_triangulate_reflex() {
        let points = vec![ Point::new(0.0f32, 0.0f32),
                            Point::new(5.0f32, 0.0f32),
                            Point::new(2.0f32, 2.0f32),
                            Point::new(5.0f32, 4.0f32),
                            Point::new(0.0f32, 4.0f32) ];

        let triangles = triangulate(&points).unwrap();

        for i in 0..(triangles.len() / 3) {
            println!("{}, {}, {}", triangles[i*3], triangles[i*3+1], triangles[i*3+2]);
        }

        assert!(is_same_triangulation(&triangles, vec![(0, 1, 2), (0, 2, 4), (4, 2, 3)]));
    }
}<|endoftext|>


<commit_before>mod crypt;

use std::path::Path;

extern crate rustc_serialize;
use rustc_serialize::json;

extern crate uuid;
use uuid::Uuid;

use std::io::prelude::*;
use std::fs;

const KEYS_PATH: &'static str = "/home/andrey/Documents/storages/keys";
const DATA_PATH: &'static str = "/home/andrey/Documents/storages/data";
const MAPS_PATH: &'static str = "/home/andrey/Documents/storages/maps";

#[derive(RustcDecodable, RustcEncodable)]
struct StorableKey {
    key: Box<[u8]>,
    iv: Box<[u8]>
}

#[derive(RustcDecodable, RustcEncodable)]
struct StorableData {
    ciphertext: Vec<u8>
}

#[derive(RustcDecodable, RustcEncodable)]
struct StorableMap {
    key_id: Uuid,
    data_id: Uuid,
    tag: Box<[u8]>
}

pub fn dump(external_id: String, data: Vec<u8>) -> Result<(), String>  {

    let result = crypt::encrypt(external_id.as_bytes(), &data);

    let key_id = store_key(KEYS_PATH.to_string(), StorableKey { key: result.key, iv: result.iv }).unwrap();
    let data_id = store_data(DATA_PATH.to_string(), StorableData { ciphertext: result.ciphertext }).unwrap();
    let external_uuid = Uuid::parse_str(&external_id).unwrap();
    store_map(MAPS_PATH.to_string(), external_uuid, StorableMap { key_id: key_id, data_id: data_id, tag: result.tag })
}

fn prepare_full_path(path_prefix: &String, path_key_string: &String) -> String {
    let path_key_chars = path_key_string.chars();
    let first_suffix = path_key_chars.clone().take(2).collect::<String>();
    let second_suffix = path_key_chars.clone().skip(2).take(2).collect::<String>();
    format!("{}/{}/{}", path_prefix, first_suffix, second_suffix)
}

fn store_json_string(path_prefix: String, path_key: Uuid, json: String) -> Result<(), String> {
    let full_path = prepare_full_path(&path_prefix, &path_key.to_simple_string());
    fs::create_dir_all(&full_path).ok();
    let mut file = fs::File::create(Path::new(&format!("{}/{}.json", full_path, path_key.to_string()))).ok().expect("Cannot create file");
    match file.write_all(json.as_bytes()) {
        Ok(_) => Ok(()),
        Err(error) => Err(format!("Error: {}", error))
    }
}

fn store_data(path_prefix: String, storable: StorableData) -> Result<Uuid, String> {
    let id = Uuid::new_v4();
    let json = json::encode(&storable).unwrap();
    store_json_string(path_prefix, id, json).ok();
    Ok(id)
}

fn store_key(path_prefix: String, storable: StorableKey) -> Result<Uuid, String> {
    let id = Uuid::new_v4();
    let json = json::encode(&storable).unwrap();
    store_json_string(path_prefix, id, json).ok();
    Ok(id)
}

fn store_map(path_prefix: String, external_id: Uuid, storable: StorableMap) -> Result<(), String> {
    let id = external_id;
    let json = json::encode(&storable).unwrap();
    store_json_string(path_prefix, id, json).ok();
    Ok(())
}

pub fn load(external_id: &String) -> Vec<u8> {
    let maps_path = prepare_full_path(&MAPS_PATH.to_string(), external_id);
    vec![0]
}
<commit_msg>Much more compact.<commit_after>mod crypt;

use std::path::Path;

extern crate rustc_serialize;
use rustc_serialize::json;

extern crate uuid;
use uuid::Uuid;

use std::io::prelude::*;
use std::fs;

const KEYS_PATH: &'static str = "/home/andrey/Documents/storages/keys";
const DATA_PATH: &'static str = "/home/andrey/Documents/storages/data";
const MAPS_PATH: &'static str = "/home/andrey/Documents/storages/maps";

#[derive(RustcDecodable, RustcEncodable)]
struct StorableKey {
    key: Box<[u8]>,
    iv: Box<[u8]>
}

#[derive(RustcDecodable, RustcEncodable)]
struct StorableData {
    ciphertext: Vec<u8>
}

#[derive(RustcDecodable, RustcEncodable)]
struct StorableMap {
    key_id: Uuid,
    data_id: Uuid,
    tag: Box<[u8]>
}

pub fn dump(external_id: String, data: Vec<u8>) -> Result<(), String>  {
    let result = crypt::encrypt(external_id.as_bytes(), &data);

    let key_id = store_key(KEYS_PATH.to_string(), StorableKey { key: result.key, iv: result.iv }).unwrap();
    let data_id = store_data(DATA_PATH.to_string(), StorableData { ciphertext: result.ciphertext }).unwrap();
    let external_uuid = Uuid::parse_str(&external_id).unwrap();
    store_map(MAPS_PATH.to_string(), external_uuid, StorableMap { key_id: key_id, data_id: data_id, tag: result.tag })
}

fn prepare_full_path(path_prefix: &String, path_key_string: &String) -> String {
    format!("{}/{}/{}/{}", path_prefix, path_key_string[0..2].to_string(), path_key_string[2..4].to_string(), path_key_string[4..6].to_string())
}

fn store_json_string(path_prefix: String, path_key: Uuid, json: String) -> Result<(), String> {
    let full_path = prepare_full_path(&path_prefix, &path_key.to_simple_string());
    fs::create_dir_all(&full_path).ok();
    let mut file = fs::File::create(Path::new(&format!("{}/{}.json", full_path, path_key.to_string()))).ok().expect("Cannot create file");
    match file.write_all(json.as_bytes()) {
        Ok(_) => Ok(()),
        Err(error) => Err(format!("Error: {}", error))
    }
}

fn store_data(path_prefix: String, storable: StorableData) -> Result<Uuid, String> {
    let id = Uuid::new_v4();
    let json = json::encode(&storable).unwrap();
    store_json_string(path_prefix, id, json).ok();
    Ok(id)
}

fn store_key(path_prefix: String, storable: StorableKey) -> Result<Uuid, String> {
    let id = Uuid::new_v4();
    let json = json::encode(&storable).unwrap();
    store_json_string(path_prefix, id, json).ok();
    Ok(id)
}

fn store_map(path_prefix: String, external_id: Uuid, storable: StorableMap) -> Result<(), String> {
    let id = external_id;
    let json = json::encode(&storable).unwrap();
    store_json_string(path_prefix, id, json).ok();
    Ok(())
}

pub fn load(external_id: &String) -> Vec<u8> {
    let maps_path = prepare_full_path(&MAPS_PATH.to_string(), external_id);
    vec![0]
}
<|endoftext|>