首页
社区
课程
招聘
[翻译]使用Rust进行系统编程 - part2
发表于: 2023-3-28 17:05 7799

[翻译]使用Rust进行系统编程 - part2

2023-3-28 17:05
7799

前言

前段时间我本想写更多关于如何使用Rust进行系统编程的文章,但是我完全不知道该写什么。现在我偶尔会使用Rust,但是我自我感觉除了ptrace和共享内存访问方面之外,我对系统编程知之甚少。
我认为学习系统编程的最佳途径是The Linux Programming Interface,这本书的目录给了我写作的灵感,不过我认为大部分内容枯燥无味,尽管如此,我还是想要尝试一下这些内容,如果最后事实证明确实很枯燥,那我便另寻它路。
我在此申明我并不是想要将那些C代码重写成Rust——这毫无意义并且有可能侵犯版权。我想要做的是提供一个Rust的视角,并且我十分鼓励你们阅读此书

文件操作

书中一开始就提到了文件操作的问题,所以我们也以文件操作开始
下面的代码实现了打开一个文件并将内容读到缓冲区中,这些代码与C有很大的不同,但最后的运行结果是相同的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use std::fs::File;
use std::io::Read;
use std::path::Path;
 
fn main() {
    let file_path = Path::new("test.txt");
 
    match File::open(file_path) {
        Ok(mut file) => {
            // Read to a fil
            let file_length = file.metadata().unwrap().len();
            let mut buf = vec![0; file_length as usize];
            file.read(&mut buf).unwrap();
            println!(">> {:?}", buf);
        },
        Err(err) => {
            eprintln!("Failed to open {} -> {}", file_path.display(), err);
        }
    }
}

你可能一眼就看到了File::open并且想要阅读官方文档,不要着急,让我们先了解一些基本知识

参数

函数定义如下:fn open<P:AsRef<Path>>(path: P) -> Result<File>。与C中的文件操作不同的是,C中的file open的第一个参数是一个字符数组,但在Rust中,我们需要提供一个路径;你或许会好奇这有什么区别。

事实上不仅仅是path,任何对象在解读的时候都会返回一个path

 

其实path是OsString的一个薄包装器,path能获得的不仅是目录,还包括了文件名等信息,这极大地提高了效率
Rust使用的字符编码是UTF-8,这导致字符中可能包含零,而在才做系统中,文件名不能包含零;而path可以自动的处理这些问题

返回值和错误

接下来需要讲的是返回值;在C中,当fopen出错时会返回一个“-1”,这种返回并不准确;Rust则使用Result取而代之,这种方式的返回值会解压到文件处理程序或错误中
至于错误,则是由io::Error其中的值来表示。在示例代码中,我们并不区分错误类型,我们只是捕获错误,并显示给用户;如果你想以不同的方式处理错误,可以自行增加处理代码

一个有趣的事实:我们使用eprintln来打印错误,它与println的唯一区别是eprintln写入的是stderr而不是stdout,毕竟这些内容也是文件描述符的一部分

 

若我们没有遇到任何错误时,我们会得到一个文件处理程序——这里由文件结构表示;它将允许我们对文件执行各种操作,你可能会好奇,为什么我必须将文件标记为可变的?可变与文件可写是否有某种联系?
简单来说,文件可变与文件本身的可写性没有联系——我们打开文件使得文件只能是可读的;可变性使得文件本身可以与程序同步,因为首先访问的状态和同步是程序无法控制的;其次,阅读通过改变文件的偏移量来改变文件结构的状态

元数据

当文件被打开时,我们会想要获得关于这个文件的信息,比如:文件所有者,文件的长度等等。为此,我们有一个metadata()调用,它返回一个metadata结构(同样是Result形式的);有了这些信息后,我们便可以执行更多的操作了,关于其他信息可以在文档中查看
标准的metadata对象是平台无关的,这意味着你只能使用通用的处理方法(比如长度或文件类型);如果想要使用某些特定的功能,可以通过导入std::os::unix::fs::MetadataExt来启用它,启用后便可以访问像uid、gid、atime等其他字段

文件读取

对于普通的文件描述符,可以通过调用read()函数来读取文件的内容。通过查看函数签名,我们看到它将目标缓冲区作为单个参数;此处需要注意,缓冲区使用前需要先初始化,否则会报错
当调用read()函数时,我们会从游标(也称文件偏移量)开始读取,直到文件的末尾;如果出现缓冲区比文件大的情况,则读取与缓冲区大小相同的字节,并更新游标
read()函数有许多变体,例如将内容转换为字符串的read_to_string()或将迭代给定文件中的各个字节的bytes()。Python程序员估计想不到还能读取单行内容,这在非缓冲区模式中是不存在的,后面我会给出解决方法

关闭文件

细心的读者肯能注意到我们在这个例子中并没有关闭文件。这是因为文件变量超出范围时,会调用Drop trait for File,文件将会被关闭;这有点儿类似于Python的上下文管理
有一点需要注意的是:在文件关闭的过程中遇到的任何错误都会被忽略。如果想要手动处理这些错误,则需要调用sync_all()函数

文件操作权限

上面的代码中,我们只是用了File::open()来打开文件,事实上File::open()只是一个别名,他们依赖的是std::fs::OpenOptions构建器,这个构建器还可以实现其他操作,例如:创建文件或以仅追加模式打开文件;
样例代码如下

1
2
3
4
5
6
7
8
9
10
fn main() {
    let file_path = Path::new("test.txt");
 
    let file = OpenOptions::new()
                .read(true)
                .write(true)
                .create(true)
                .truncate(true)
                .open(file_path);
}

在上面的样例代码中,我们正在创建一个可写可读的新文件,并且将其大小截断为零

缓冲区访问

有时标准读取的效率并不高。假设我们想要用逐字节迭代的方式,将换行符之前的所有内容读取到缓冲区中,这意味着我们正在为读取的每个字节执行系统调用
不过在Rust中有更好的方法实现这一点;我们可以用std::io::BufReader来包装一个文件处理程序,这会创建一个底层的内存缓冲区(目前是8192个字节),它将被一个read()调用填满,并允许用户操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() {
    let file_path = Path::new("/etc/passwd");
 
    match File::open(file_path) {
        Ok(file) => {
            let mut reader = BufReader::new(file);
 
            for _ in 0..5 {
                let mut line = String::new();
                reader.read_line(&mut line).unwrap();
                print!("=> {}", line);
            }
        }
        Err(err) => {
            eprintln!("Problem reading from {}: {}", file_path.display(), err);
        }
 
    }
}

如果我们遍历缓冲区,底层机制将再次调用read()以使用当前文件偏移量重新填充缓冲区;当进行多个小而重复的操作时,缓冲读取器(或写入器)会是更好的选择

结束语

本系列的第一部分到此结束,正如开头所说,我正在计划更多的内容,如果你喜欢此类内容,请告诉,我会写更多(译者:我也会翻译更多)。为了完整起见,我可能应该写一些关于目录、扩展属性以及监控文件的内容。

译者言

因本人翻译水平有限,如有错误之处,请斧正
原文链接


[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 1
支持
分享
最新回复 (3)
雪    币: 15
活跃值: (298)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
不错不错,多谢楼主
2023-3-28 19:02
0
雪    币: 218
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
当rust还在抠细节的时候,别人的项目都发布了,然后最后做出来的时候,别人都抢占完市场,迭代了几个版本。所谓的安全,欺骗了自己,恶心了别人。
2023-3-30 08:27
1
雪    币: 15661
活跃值: (18963)
能力值: ( LV12,RANK:300 )
在线值:
发帖
回帖
粉丝
4
mb_axderzki 当rust还在抠细节的时候,别人的项目都发布了,然后最后做出来的时候,别人都抢占完市场,迭代了几个版本。所谓的安全,欺骗了自己,恶心了别人。
说出你的故事
2023-3-30 09:32
0
游客
登录 | 注册 方可回帖
返回