首页  >  文章  >  后端开发  >  玩转 Rust:建立更安全的公司并一路享受乐趣

玩转 Rust:建立更安全的公司并一路享受乐趣

Mary-Kate Olsen
Mary-Kate Olsen原创
2024-10-21 06:07:02344浏览

Playing with Rust: Building a Safer rm and Having Fun Along the Way

欢迎来到我的YOLO系列,我将在其中展示我构建的简单工具和项目——有时是为了好玩,有时是为了解决特定问题,有时只是出于纯粹的好奇心。这里的目标不仅仅是展示一个工具;而是展示一个工具。我还将深入探讨与该过程相关的一些有趣的内容,无论是技术见解还是在制作这些小实验时学到的教训。

rrm 简介:没人需要的命令行工具

没有人要求它,也没有人想要它——但无论如何它就在这里。来认识一下 rrm,这个工具似乎只能解决我遇到的问题(但是,嘿,这可能是第 8 层问题,或者更可能是技能问题!)。

rrm 通过将文件移动到垃圾箱而不是永久删除它们,为您的命令行体验增加了一层安全性。有了可定制的宽限期,您就有机会意识到,“哎呀,我实际上需要这个!”,以免为时已晚。

此外,rrm 不依赖外部配置文件或跟踪系统来管理已删除的文件。相反,它利用文件系统的扩展属性直接在已删除的项目中存储基本元数据(例如原始文件路径和删除时间)。

您可能想知道,“既然有类似的、可能更好的工具,为什么我还要构建这个工具?” 嗯,答案很简单:

  • 我想玩 Rust。 构建小型、有目的的工具是探索语言和提高技能的好方法。
  • 就像开发自己的 CLI 工具作为创建心理框架的一种方式。它帮助我始终如一地处理如何为特定技术构建命令行实用程序。通过构建这些工具,我加深了对使用哪些依赖项、如何组织代码以及如何使每个工具适应语言生态系统的理解。这是一种构建心理手册的方法,用于创建适合我需求的 CLI 工具。
  • 因为 YOLO。我喜欢围绕我想解决的问题或我好奇的事情制作简单的工具或概念验证。有时,尝试是为了学习。

有趣的说明: 在使用 std::Path 时,我在 Rust 标准库中找到了一个示例,该示例使用名为 laputa

的文件夹。我知道这是天空之城的参考,但对于西班牙语使用者来说,这也是一个诅咒词,这对我来说是一个有趣的时刻!<script> // Detect dark theme var iframe = document.getElementById('tweet-1844834987184410735-190'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1844834987184410735&theme=dark" } </script>

扩展属性:在不更改文件的情况下存储元数据

当我开始构建 rrm 时,我需要一种方法来跟踪已删除文件的原始路径以及应永久删除它们的时间。我不想使用 JSON 文件或实现包含此信息的奇怪命名格式 - 特别是如果我想稍后存储更多数据。对于如此小的任务来说,数据库感觉有点大材小用。

就在那时我发现了扩展属性

什么是扩展属性?

现在,我不了解你,但我没有意识到有一个内置机制可以让你向文件添加自定义元数据,大多数 Linux 文件系统和类 Unix 系统(例如 macOS)都支持该机制。此功能称为扩展文件属性。不同的系统有其自身的限制,例如可以添加多少数据或使用的特定命名空间,但它们确实允许您存储用户定义的元数据。

扩展属性本质上是与文件和目录永久关联的名称:值对。正如我之前提到的,系统处理此问题的方式有所不同。例如,在 Linux 中,名称以名称空间标识符开头。有四个这样的命名空间:安全、系统、可信和用户。在 Linux 中,名称以其中之一开头,后跟一个点(“.”),然后是一个以 null 结尾的字符串。在 macOS 上,情况有些不同。 macOS 根本不需要命名空间,这要归功于它的统一元数据方法,它将扩展属性视为直接与文件关联的附加元数据,而无需进行分类。

在这个小型 CLI 中,我使用 crate xattr,它支持 Linux 和 macOS。关于我之前提到的 Linux 命名空间,我们将重点关注用户命名空间,因为这些属性是供用户使用的。所以,在代码中,你会看到类似这样的内容:

/// Namespace for extended attributes (xattrs) on macOS and other operating systems.
/// On macOS, this is an empty string, while on other operating systems, it is "user.".
#[cfg(target_os = "macos")]
const XATTR_NAMESPACE: &str = "";
#[cfg(not(target_os = "macos"))]
const XATTR_NAMESPACE: &str = "user.";

...

    fn set_attr(&self, path: &Path, attr: &str, value: &str) -> Result<()> {
        let attr_name = format!("{}{}", XATTR_NAMESPACE, attr);
        ...
    }

Rust 中的 #[cfg(target_os = "macos")] 属性用于根据目标操作系统有条件地编译代码。在这种情况下,它确保仅在针对 macOS 进行编译时才包含代码块。这是相关的,因为如前所述,macOS 不需要扩展属性的命名空间,因此 XATTR_NAMESPACE 设置为空字符串。对于其他操作系统,命名空间设置为“user.”。这种条件编译允许代码无缝适应不同平台,使 CLI 与 Linux 和 macOS 交叉兼容。

我发现扩展属性非常酷的一件事是它们不会修改文件本身。元数据位于单独的磁盘空间中,由索引节点引用。这意味着文件的实际内容保持不变。例如,如果我们创建一个简单的文件并使用 shasum 来获取其校验和:

inode(索引节点)是Unix 风格文件系统中的一种数据结构,用于描述文件系统对象,例如文件或目录。链接

/// Namespace for extended attributes (xattrs) on macOS and other operating systems.
/// On macOS, this is an empty string, while on other operating systems, it is "user.".
#[cfg(target_os = "macos")]
const XATTR_NAMESPACE: &str = "";
#[cfg(not(target_os = "macos"))]
const XATTR_NAMESPACE: &str = "user.";

...

    fn set_attr(&self, path: &Path, attr: &str, value: &str) -> Result<()> {
        let attr_name = format!("{}{}", XATTR_NAMESPACE, attr);
        ...
    }

使用 rrm 删除文件后,我们可以列出已删除的文件,并看到该文件已被移至垃圾箱,元数据完好无损:

$ cat a.txt
https://www.kungfudev.com/

$ shasum a.txt
e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5  a.txt

如您所见,文件名已更改为 UUID。这样做是为了避免删除同名文件时发生名称冲突。通过为每个文件分配唯一的标识符,rrm 确保每个已删除的文件,即使它们具有相同的名称,也可以毫无问题地跟踪和恢复。

我们可以导航到垃圾文件夹并检查文件以确认其内容保持不变:

$ rrm rm a.txt

$ rrm list
╭──────────────────────────────────────────────────────┬──────────────────────────────────────┬──────┬─────────────────────╮
│ Original Path                                        ┆ ID                                   ┆ Kind ┆ Deletion Date       │
╞══════════════════════════════════════════════════════╪══════════════════════════════════════╪══════╪═════════════════════╡
│ /Users/douglasmakey/workdir/personal/kungfudev/a.txt ┆ 3f566788-75dc-4674-b069-0faeaa86aa55 ┆ File ┆ 2024-10-27 04:10:19 │
╰──────────────────────────────────────────────────────┴──────────────────────────────────────┴──────┴─────────────────────╯

此外,通过在 macOS 上使用 xattr,我们可以验证文件是否具有其元数据,例如删除日期和原始路径:

$ shasum 3f566788-75dc-4674-b069-0faeaa86aa55
e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5  3f566788-75dc-4674-b069-0faeaa86aa55

您可以想象使用此元数据进行简单验证或操作的潜在用例范围。由于扩展属性无需修改文件本身即可工作,因此它们允许您检查文件完整性或执行其他操作,而不会影响原始内容。

这只是对扩展属性以及它们在这个项目中的使用方式的一个小介绍。这并不是一个深入的解释,但如果您有兴趣了解更多信息,那里有大量详细的资源。以下是一些关于该主题的最有用且描述良好的资源的链接:

  • https://wiki.archlinux.org/title/Extended_attributes
  • https://man7.org/linux/man-pages/man7/xattr.7.html
  • https://en.wikipedia.org/wiki/Extended_file_attributes

Rust 中的模拟:探索用于测试的模拟

我用 Go 工作了几年,我开始喜欢某些模式——mocking 就是其中之一。在 Go 中,如果可以避免不必要的导入或给我更大的灵活性,我通常会自己实现一些东西。我已经习惯了这种方法,以至于当我开始用 Rust 编写测试时,我发现自己更喜欢手动模拟某些东西,比如创建特征的模拟实现。

例如,在这个小型 CLI 中,我创建了一个特征,将垃圾管理器与其与扩展属性交互的方式分离。该特征名为 ExtendedAttributes,最初是为了测试目的,但也是因为我不确定是否会使用 xattr 还是其他实现。因此,我定义了以下特征:

$ xattr -l 3f566788-75dc-4674-b069-0faeaa86aa55
deletion_date: 2024-10-27T04:10:19.875614+00:00
original_path: /Users/douglasmakey/workdir/personal/kungfudev/a.txt

在 Go 中,我将创建如下所示的内容,它提供了前面提到的接口的简单实现。下面的代码很简单,生成时没有太多考虑,只是为了示例:

/// Namespace for extended attributes (xattrs) on macOS and other operating systems.
/// On macOS, this is an empty string, while on other operating systems, it is "user.".
#[cfg(target_os = "macos")]
const XATTR_NAMESPACE: &str = "";
#[cfg(not(target_os = "macos"))]
const XATTR_NAMESPACE: &str = "user.";

...

    fn set_attr(&self, path: &Path, attr: &str, value: &str) -> Result<()> {
        let attr_name = format!("{}{}", XATTR_NAMESPACE, attr);
        ...
    }

然后,我将使用我的模拟并注入每个测试所需的特定行为。再次强调,这是简单的代码,只是为了示例:

$ cat a.txt
https://www.kungfudev.com/

$ shasum a.txt
e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5  a.txt

我已经习惯了 Go 中的这种模式,并且我打算继续使用它。但我也在 Rust 中做了类似的事情。对于这个项目,我决定尝试一下mockall crate,我发现它非常有用。

首先,我使用了模拟!宏来手动模拟我的结构。我知道mockall有一个automock功能,但我更喜欢直接在我的测试中使用它来定义mock结构。 请告诉我这是否常见,或者社区对此有不同的标准。

$ rrm rm a.txt

$ rrm list
╭──────────────────────────────────────────────────────┬──────────────────────────────────────┬──────┬─────────────────────╮
│ Original Path                                        ┆ ID                                   ┆ Kind ┆ Deletion Date       │
╞══════════════════════════════════════════════════════╪══════════════════════════════════════╪══════╪═════════════════════╡
│ /Users/douglasmakey/workdir/personal/kungfudev/a.txt ┆ 3f566788-75dc-4674-b069-0faeaa86aa55 ┆ File ┆ 2024-10-27 04:10:19 │
╰──────────────────────────────────────────────────────┴──────────────────────────────────────┴──────┴─────────────────────╯

我发现mockall非常有用,它允许我将特定行为注入到我的测试中,而无需使用旧模式的冗长。

$ shasum 3f566788-75dc-4674-b069-0faeaa86aa55
e4c51607d5e7494143ffa5a20b73aedd4bc5ceb5  3f566788-75dc-4674-b069-0faeaa86aa55

正如我们所见,mockall 使我们能够使用其模拟方法为测试定义特定行为:

  • MockXattrManager::new() 这将创建模拟对象 MockXattrManager 的新实例,用于模拟 XattrManager 的行为以进行测试。
  • xattr_manager.expect_set_attr() 这设置了在测试期间调用 set_attr 方法的预期。接下来定义此方法的预期行为。
  • 与(...) with 方法指定调用 set_attr 时所需的参数。在本例中,它需要三个参数,并使用 in_iter 来指示每个参数应与提供的向量中的值之一匹配。这允许测试的灵活性,因为它检查参数是否是传递的向量中的值之一,而不是单个精确匹配。
  • 次(4) 这指定 set_attr 方法预计在测试期间被调用四次。
  • 返回(|_, _, _| 好的(())) 这告诉模拟调用 set_attr 时返回什么。在这种情况下,无论参数如何,它都会返回 Ok(())(|_、_、_| 表示忽略参数)。这模拟了set_attr的成功执行。

你们中的一些人可能会觉得这非常基本或没那么有趣,但正如我提到的,在这个 YOLO 系列中,我正在分享我觉得有趣或只是想谈论的事情。我不太喜欢在 Go 中使用这种库,部分原因是 Go 的限制,但在 Rust 中,我发现 mockall 非常有用。它甚至让我想起了以前使用 Python 的日子。

再次强调,本节并不是要解释 Rust 或 mockall 中的模拟。我确信有很多很棒的资源详细介绍了它。我只是想简单提一下。

总结一下

在这篇文章中,我分享了构建 rrm 背后的一些原因以及我在此过程中使用的工具。从使用扩展属性来简化元数据处理,到尝试使用模拟箱进行 Rust 测试,这些都激起了我的兴趣。

这个 YOLO 系列的目标是强调构建简单工具所带来的乐趣和学习。我希望您在这里找到有用的东西,我期待在未来的帖子中分享更多的项目和见解。一如既往,欢迎反馈!

编码愉快!

以上是玩转 Rust:建立更安全的公司并一路享受乐趣的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn