Skip to content

Commit 95c542e

Browse files
committed
auto merge of #8655 : olsonjeffery/rust/newrt_file_io, r=pcwalton,brson
This PR includes the addition of the essential CRUD functionality exposed as a part of the `uv_fs_*` api. There's a lot more to be done, but the essential abstractions are in place and can be easily expanded. A summary: * `rt::io::file::FileStream` is fleshed out and behaves as a *non-positional* file stream (that is, it has a cursor that can be viewed/changed via `tell` and `seek` * The underlying abstraction in `RtioFileStream` exposes pairs of `read(), write()` and `pread(), pwrite()`. The latter two take explicit `offset` params and don't respect the current cursor location in a file afaik. They both use the same underlying libuv impl * Because libuv explicitly does *not* support `seek`/`tell` operations, these are impl'd in `UvFileStream` by using `lseek(2)` on the raw file descriptor. * I did my best to flesh out and adhere to the stubbing that was already present in `rt::io::file` and the tests should back that up. There may be things missing. * All of the work to test `seek`/`tell` is done in `rt::io::file`, even though the actual impl is down in `rt::uv::uvio`. * We have the ability to spin up an `~RtioFileStream` from a raw file descriptor. This would be useful for interacting with stdin and stdout via newrt. * The lowest level abstractions (in `rt::uv::file`) support fully synchronous/blocking interactions with the uv API and there is a CRUD test using it. This may also be useful for blocking printf, if desired (the default would be non-blocking and uses libuv's io threadpool) There are a few polish things I need to do still (the foremost that I know of is undefined behavior when seek'ing beyond the file's boundary). After this lands, I want to move on to mapping more of the `uv_fs_*` API (especially `uv_fs_stat`). Also a few people have mentioned interest in `uv_pipe_t` support. I'm open to suggestions.
2 parents 23bfa60 + b7cbd8a commit 95c542e

File tree

9 files changed

+1156
-55
lines changed

9 files changed

+1156
-55
lines changed

src/libstd/rt/io/file.rs

Lines changed: 303 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,69 +11,332 @@
1111
use prelude::*;
1212
use super::support::PathLike;
1313
use super::{Reader, Writer, Seek};
14-
use super::SeekStyle;
14+
use super::{SeekSet, SeekCur, SeekEnd, SeekStyle};
15+
use rt::rtio::{RtioFileStream, IoFactory, IoFactoryObject};
16+
use rt::io::{io_error, read_error, EndOfFile,
17+
FileMode, FileAccess, Open, Read, Create, ReadWrite};
18+
use rt::local::Local;
19+
use rt::test::*;
1520

16-
/// # FIXME #7785
17-
/// * Ugh, this is ridiculous. What is the best way to represent these options?
18-
enum FileMode {
19-
/// Opens an existing file. IoError if file does not exist.
20-
Open,
21-
/// Creates a file. IoError if file exists.
22-
Create,
23-
/// Opens an existing file or creates a new one.
24-
OpenOrCreate,
25-
/// Opens an existing file or creates a new one, positioned at EOF.
26-
Append,
27-
/// Opens an existing file, truncating it to 0 bytes.
28-
Truncate,
29-
/// Opens an existing file or creates a new one, truncating it to 0 bytes.
30-
CreateOrTruncate,
21+
/// Open a file for reading/writing, as indicated by `path`.
22+
pub fn open<P: PathLike>(path: &P,
23+
mode: FileMode,
24+
access: FileAccess
25+
) -> Option<FileStream> {
26+
let open_result = unsafe {
27+
let io = Local::unsafe_borrow::<IoFactoryObject>();
28+
(*io).fs_open(path, mode, access)
29+
};
30+
match open_result {
31+
Ok(fd) => Some(FileStream {
32+
fd: fd,
33+
last_nread: -1
34+
}),
35+
Err(ioerr) => {
36+
io_error::cond.raise(ioerr);
37+
None
38+
}
39+
}
3140
}
3241

33-
enum FileAccess {
34-
Read,
35-
Write,
36-
ReadWrite
42+
/// Unlink (remove) a file from the filesystem, as indicated
43+
/// by `path`.
44+
pub fn unlink<P: PathLike>(path: &P) {
45+
let unlink_result = unsafe {
46+
let io = Local::unsafe_borrow::<IoFactoryObject>();
47+
(*io).fs_unlink(path)
48+
};
49+
match unlink_result {
50+
Ok(_) => (),
51+
Err(ioerr) => {
52+
io_error::cond.raise(ioerr);
53+
}
54+
}
3755
}
3856

39-
pub struct FileStream;
57+
/// Abstraction representing *positional* access to a file. In this case,
58+
/// *positional* refers to it keeping an encounter *cursor* of where in the
59+
/// file a subsequent `read` or `write` will begin from. Users of a `FileStream`
60+
/// can `seek` to move the cursor to a given location *within the bounds of the
61+
/// file* and can ask to have the `FileStream` `tell` them the location, in
62+
/// bytes, of the cursor.
63+
///
64+
/// This abstraction is roughly modeled on the access workflow as represented
65+
/// by `open(2)`, `read(2)`, `write(2)` and friends.
66+
///
67+
/// The `open` and `unlink` static methods are provided to manage creation/removal
68+
/// of files. All other methods operatin on an instance of `FileStream`.
69+
pub struct FileStream {
70+
fd: ~RtioFileStream,
71+
last_nread: int,
72+
}
4073

4174
impl FileStream {
42-
pub fn open<P: PathLike>(_path: &P,
43-
_mode: FileMode,
44-
_access: FileAccess
45-
) -> Option<FileStream> {
46-
fail!()
47-
}
4875
}
4976

5077
impl Reader for FileStream {
51-
fn read(&mut self, _buf: &mut [u8]) -> Option<uint> {
52-
fail!()
78+
fn read(&mut self, buf: &mut [u8]) -> Option<uint> {
79+
match self.fd.read(buf) {
80+
Ok(read) => {
81+
self.last_nread = read;
82+
match read {
83+
0 => None,
84+
_ => Some(read as uint)
85+
}
86+
},
87+
Err(ioerr) => {
88+
// EOF is indicated by returning None
89+
if ioerr.kind != EndOfFile {
90+
read_error::cond.raise(ioerr);
91+
}
92+
return None;
93+
}
94+
}
5395
}
5496

5597
fn eof(&mut self) -> bool {
56-
fail!()
98+
self.last_nread == 0
5799
}
58100
}
59101

60102
impl Writer for FileStream {
61-
fn write(&mut self, _v: &[u8]) { fail!() }
103+
fn write(&mut self, buf: &[u8]) {
104+
match self.fd.write(buf) {
105+
Ok(_) => (),
106+
Err(ioerr) => {
107+
io_error::cond.raise(ioerr);
108+
}
109+
}
110+
}
62111

63-
fn flush(&mut self) { fail!() }
112+
fn flush(&mut self) {
113+
match self.fd.flush() {
114+
Ok(_) => (),
115+
Err(ioerr) => {
116+
read_error::cond.raise(ioerr);
117+
}
118+
}
119+
}
64120
}
65121

66122
impl Seek for FileStream {
67-
fn tell(&self) -> u64 { fail!() }
123+
fn tell(&self) -> u64 {
124+
let res = self.fd.tell();
125+
match res {
126+
Ok(cursor) => cursor,
127+
Err(ioerr) => {
128+
read_error::cond.raise(ioerr);
129+
return -1;
130+
}
131+
}
132+
}
133+
134+
fn seek(&mut self, pos: i64, style: SeekStyle) {
135+
match self.fd.seek(pos, style) {
136+
Ok(_) => {
137+
// successful seek resets EOF indicator
138+
self.last_nread = -1;
139+
()
140+
},
141+
Err(ioerr) => {
142+
read_error::cond.raise(ioerr);
143+
}
144+
}
145+
}
146+
}
68147

69-
fn seek(&mut self, _pos: i64, _style: SeekStyle) { fail!() }
148+
fn file_test_smoke_test_impl() {
149+
do run_in_newsched_task {
150+
let message = "it's alright. have a good time";
151+
let filename = &Path("./tmp/file_rt_io_file_test.txt");
152+
{
153+
let mut write_stream = open(filename, Create, ReadWrite).unwrap();
154+
write_stream.write(message.as_bytes());
155+
}
156+
{
157+
use str;
158+
let mut read_stream = open(filename, Open, Read).unwrap();
159+
let mut read_buf = [0, .. 1028];
160+
let read_str = match read_stream.read(read_buf).unwrap() {
161+
-1|0 => fail!("shouldn't happen"),
162+
n => str::from_bytes(read_buf.slice_to(n))
163+
};
164+
assert!(read_str == message.to_owned());
165+
}
166+
unlink(filename);
167+
}
70168
}
71169

72170
#[test]
73-
#[ignore]
74-
fn super_simple_smoke_test_lets_go_read_some_files_and_have_a_good_time() {
75-
let message = "it's alright. have a good time";
76-
let filename = &Path("test.txt");
77-
let mut outstream = FileStream::open(filename, Create, Read).unwrap();
78-
outstream.write(message.as_bytes());
171+
fn file_test_io_smoke_test() {
172+
file_test_smoke_test_impl();
173+
}
174+
175+
fn file_test_invalid_path_opened_without_create_should_raise_condition_impl() {
176+
do run_in_newsched_task {
177+
let filename = &Path("./tmp/file_that_does_not_exist.txt");
178+
let mut called = false;
179+
do io_error::cond.trap(|_| {
180+
called = true;
181+
}).inside {
182+
let result = open(filename, Open, Read);
183+
assert!(result.is_none());
184+
}
185+
assert!(called);
186+
}
187+
}
188+
#[test]
189+
fn file_test_io_invalid_path_opened_without_create_should_raise_condition() {
190+
file_test_invalid_path_opened_without_create_should_raise_condition_impl();
191+
}
192+
193+
fn file_test_unlinking_invalid_path_should_raise_condition_impl() {
194+
do run_in_newsched_task {
195+
let filename = &Path("./tmp/file_another_file_that_does_not_exist.txt");
196+
let mut called = false;
197+
do io_error::cond.trap(|_| {
198+
called = true;
199+
}).inside {
200+
unlink(filename);
201+
}
202+
assert!(called);
203+
}
204+
}
205+
#[test]
206+
fn file_test_iounlinking_invalid_path_should_raise_condition() {
207+
file_test_unlinking_invalid_path_should_raise_condition_impl();
208+
}
209+
210+
fn file_test_io_non_positional_read_impl() {
211+
do run_in_newsched_task {
212+
use str;
213+
let message = "ten-four";
214+
let mut read_mem = [0, .. 8];
215+
let filename = &Path("./tmp/file_rt_io_file_test_positional.txt");
216+
{
217+
let mut rw_stream = open(filename, Create, ReadWrite).unwrap();
218+
rw_stream.write(message.as_bytes());
219+
}
220+
{
221+
let mut read_stream = open(filename, Open, Read).unwrap();
222+
{
223+
let read_buf = read_mem.mut_slice(0, 4);
224+
read_stream.read(read_buf);
225+
}
226+
{
227+
let read_buf = read_mem.mut_slice(4, 8);
228+
read_stream.read(read_buf);
229+
}
230+
}
231+
unlink(filename);
232+
let read_str = str::from_bytes(read_mem);
233+
assert!(read_str == message.to_owned());
234+
}
235+
}
236+
237+
#[test]
238+
fn file_test_io_non_positional_read() {
239+
file_test_io_non_positional_read_impl();
240+
}
241+
242+
fn file_test_io_seeking_impl() {
243+
do run_in_newsched_task {
244+
use str;
245+
let message = "ten-four";
246+
let mut read_mem = [0, .. 4];
247+
let set_cursor = 4 as u64;
248+
let mut tell_pos_pre_read;
249+
let mut tell_pos_post_read;
250+
let filename = &Path("./tmp/file_rt_io_file_test_seeking.txt");
251+
{
252+
let mut rw_stream = open(filename, Create, ReadWrite).unwrap();
253+
rw_stream.write(message.as_bytes());
254+
}
255+
{
256+
let mut read_stream = open(filename, Open, Read).unwrap();
257+
read_stream.seek(set_cursor as i64, SeekSet);
258+
tell_pos_pre_read = read_stream.tell();
259+
read_stream.read(read_mem);
260+
tell_pos_post_read = read_stream.tell();
261+
}
262+
unlink(filename);
263+
let read_str = str::from_bytes(read_mem);
264+
assert!(read_str == message.slice(4, 8).to_owned());
265+
assert!(tell_pos_pre_read == set_cursor);
266+
assert!(tell_pos_post_read == message.len() as u64);
267+
}
268+
}
269+
#[test]
270+
fn file_test_io_seek_and_tell_smoke_test() {
271+
file_test_io_seeking_impl();
272+
}
273+
274+
fn file_test_io_seek_and_write_impl() {
275+
use io;
276+
do run_in_newsched_task {
277+
use str;
278+
let initial_msg = "food-is-yummy";
279+
let overwrite_msg = "-the-bar!!";
280+
let final_msg = "foo-the-bar!!";
281+
let seek_idx = 3;
282+
let mut read_mem = [0, .. 13];
283+
let filename = &Path("./tmp/file_rt_io_file_test_seek_and_write.txt");
284+
{
285+
let mut rw_stream = open(filename, Create, ReadWrite).unwrap();
286+
rw_stream.write(initial_msg.as_bytes());
287+
rw_stream.seek(seek_idx as i64, SeekSet);
288+
rw_stream.write(overwrite_msg.as_bytes());
289+
}
290+
{
291+
let mut read_stream = open(filename, Open, Read).unwrap();
292+
read_stream.read(read_mem);
293+
}
294+
unlink(filename);
295+
let read_str = str::from_bytes(read_mem);
296+
io::println(fmt!("read_str: '%?' final_msg: '%?'", read_str, final_msg));
297+
assert!(read_str == final_msg.to_owned());
298+
}
299+
}
300+
#[test]
301+
fn file_test_io_seek_and_write() {
302+
file_test_io_seek_and_write_impl();
303+
}
304+
305+
fn file_test_io_seek_shakedown_impl() {
306+
do run_in_newsched_task {
307+
use str; // 01234567890123
308+
let initial_msg = "qwer-asdf-zxcv";
309+
let chunk_one = "qwer";
310+
let chunk_two = "asdf";
311+
let chunk_three = "zxcv";
312+
let mut read_mem = [0, .. 4];
313+
let filename = &Path("./tmp/file_rt_io_file_test_seek_shakedown.txt");
314+
{
315+
let mut rw_stream = open(filename, Create, ReadWrite).unwrap();
316+
rw_stream.write(initial_msg.as_bytes());
317+
}
318+
{
319+
let mut read_stream = open(filename, Open, Read).unwrap();
320+
321+
read_stream.seek(-4, SeekEnd);
322+
read_stream.read(read_mem);
323+
let read_str = str::from_bytes(read_mem);
324+
assert!(read_str == chunk_three.to_owned());
325+
326+
read_stream.seek(-9, SeekCur);
327+
read_stream.read(read_mem);
328+
let read_str = str::from_bytes(read_mem);
329+
assert!(read_str == chunk_two.to_owned());
330+
331+
read_stream.seek(0, SeekSet);
332+
read_stream.read(read_mem);
333+
let read_str = str::from_bytes(read_mem);
334+
assert!(read_str == chunk_one.to_owned());
335+
}
336+
unlink(filename);
337+
}
338+
}
339+
#[test]
340+
fn file_test_io_seek_shakedown() {
341+
file_test_io_seek_shakedown_impl();
79342
}

0 commit comments

Comments
 (0)