diff options
Diffstat (limited to 'lib/kernel')
-rw-r--r-- | lib/kernel/doc/src/file.xml | 6 | ||||
-rw-r--r-- | lib/kernel/src/file.erl | 18 | ||||
-rw-r--r-- | lib/kernel/src/file_io_server.erl | 8 | ||||
-rw-r--r-- | lib/kernel/src/raw_file_io_compressed.erl | 6 | ||||
-rw-r--r-- | lib/kernel/src/raw_file_io_delayed.erl | 6 | ||||
-rw-r--r-- | lib/kernel/src/raw_file_io_list.erl | 7 | ||||
-rw-r--r-- | lib/kernel/test/file_SUITE.erl | 184 |
7 files changed, 225 insertions, 10 deletions
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index b3e8149cc2..d923207f9f 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -1444,7 +1444,11 @@ f.txt: {person, "kalle", 25}. break this module's atomicity guarantees as it can race with a concurrent call to <seealso marker="#write_file_info/2"><c>write_file_info/1,2</c> - </seealso></p> + </seealso>.</p> + <p>This option has no effect when the function is + given an I/O device instead of a file name. Use + <seealso marker="#open/2"><c>open/2</c></seealso> with the + <c>raw</c> mode to obtain a file descriptor first.</p> <note> <p>As file times are stored in POSIX time on most OS, it is faster to query file information with option <c>posix</c>.</p> diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl index a0616da670..2ad2df97a8 100644 --- a/lib/kernel/src/file.erl +++ b/lib/kernel/src/file.erl @@ -239,20 +239,30 @@ make_dir(Name) -> del_dir(Name) -> check_and_call(del_dir, [file_name(Name)]). --spec read_file_info(Filename) -> {ok, FileInfo} | {error, Reason} when - Filename :: name_all(), +-spec read_file_info(File) -> {ok, FileInfo} | {error, Reason} when + File :: name_all() | io_device(), FileInfo :: file_info(), Reason :: posix() | badarg. +read_file_info(IoDevice) + when is_pid(IoDevice); is_record(IoDevice, file_descriptor) -> + read_file_info(IoDevice, []); + read_file_info(Name) -> check_and_call(read_file_info, [file_name(Name)]). --spec read_file_info(Filename, Opts) -> {ok, FileInfo} | {error, Reason} when - Filename :: name_all(), +-spec read_file_info(File, Opts) -> {ok, FileInfo} | {error, Reason} when + File :: name_all() | io_device(), Opts :: [file_info_option()], FileInfo :: file_info(), Reason :: posix() | badarg. +read_file_info(IoDevice, Opts) when is_pid(IoDevice), is_list(Opts) -> + file_request(IoDevice, {read_handle_info, Opts}); + +read_file_info(#file_descriptor{module = Module} = Handle, Opts) when is_list(Opts) -> + Module:read_handle_info(Handle, Opts); + read_file_info(Name, Opts) when is_list(Opts) -> Args = [file_name(Name), Opts], case check_args(Args) of diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl index 34d5497a4a..c03fbb548a 100644 --- a/lib/kernel/src/file_io_server.erl +++ b/lib/kernel/src/file_io_server.erl @@ -314,6 +314,14 @@ file_request(truncate, Reply -> std_reply(Reply, State) end; +file_request({read_handle_info, Opts}, + #state{handle=Handle}=State) -> + case ?CALL_FD(Handle, read_handle_info, [Opts]) of + {error,Reason}=Reply -> + {stop,Reason,Reply,State}; + Reply -> + {reply,Reply,State} + end; file_request(Unknown, #state{}=State) -> Reason = {request, Unknown}, diff --git a/lib/kernel/src/raw_file_io_compressed.erl b/lib/kernel/src/raw_file_io_compressed.erl index d5ab042d25..66c5621dd1 100644 --- a/lib/kernel/src/raw_file_io_compressed.erl +++ b/lib/kernel/src/raw_file_io_compressed.erl @@ -21,7 +21,8 @@ -export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3, position/2, write/2, pwrite/2, pwrite/3, - read_line/1, read/2, pread/2, pread/3]). + read_line/1, read/2, pread/2, pread/3, + read_handle_info/2]). %% OTP internal. -export([ipread_s32bu_p32bu/3, sendfile/8]). @@ -118,6 +119,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) -> sendfile(_,_,_,_,_,_,_,_) -> {error, enotsup}. +read_handle_info(Fd, Opts) -> + wrap_call(Fd, [Opts]). + wrap_call(Fd, Command) -> {_Owner, Pid} = get_fd_data(Fd), try gen_statem:call(Pid, Command, infinity) of diff --git a/lib/kernel/src/raw_file_io_delayed.erl b/lib/kernel/src/raw_file_io_delayed.erl index d2ad7550a1..766467437e 100644 --- a/lib/kernel/src/raw_file_io_delayed.erl +++ b/lib/kernel/src/raw_file_io_delayed.erl @@ -23,7 +23,8 @@ -export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3, position/2, write/2, pwrite/2, pwrite/3, - read_line/1, read/2, pread/2, pread/3]). + read_line/1, read/2, pread/2, pread/3, + read_handle_info/2]). %% OTP internal. -export([ipread_s32bu_p32bu/3, sendfile/8]). @@ -304,6 +305,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) -> sendfile(_,_,_,_,_,_,_,_) -> {error, enotsup}. +read_handle_info(Fd, Opts) -> + wrap_call(Fd, [Opts]). + wrap_call(Fd, Command) -> #{ pid := Pid } = get_fd_data(Fd), try gen_statem:call(Pid, Command, infinity) of diff --git a/lib/kernel/src/raw_file_io_list.erl b/lib/kernel/src/raw_file_io_list.erl index 2e16e63f0e..e4fe434e13 100644 --- a/lib/kernel/src/raw_file_io_list.erl +++ b/lib/kernel/src/raw_file_io_list.erl @@ -21,7 +21,8 @@ -export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3, position/2, write/2, pwrite/2, pwrite/3, - read_line/1, read/2, pread/2, pread/3]). + read_line/1, read/2, pread/2, pread/3, + read_handle_info/2]). %% OTP internal. -export([ipread_s32bu_p32bu/3, sendfile/8]). @@ -126,3 +127,7 @@ sendfile(Fd, Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags) -> Args = [Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags], PrivateFd = Fd#file_descriptor.data, ?CALL_FD(PrivateFd, sendfile, Args). + +read_handle_info(Fd, Opts) -> + PrivateFd = Fd#file_descriptor.data, + ?CALL_FD(PrivateFd, read_handle_info, [Opts]). diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl index 21aaefa654..9efe83d8b3 100644 --- a/lib/kernel/test/file_SUITE.erl +++ b/lib/kernel/test/file_SUITE.erl @@ -58,6 +58,8 @@ -export([ file_info_basic_file/1, file_info_basic_directory/1, file_info_bad/1, file_info_times/1, file_write_file_info/1, file_wfi_helpers/1]). +-export([ file_handle_info_basic_file/1, file_handle_info_basic_directory/1, + file_handle_info_times/1]). -export([rename/1, access/1, truncate/1, datasync/1, sync/1, read_write/1, pread_write/1, append/1, exclusive/1]). -export([ e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]). @@ -153,7 +155,10 @@ groups() -> {pos, [], [pos1, pos2, pos3]}, {file_info, [], [file_info_basic_file, file_info_basic_directory, - file_info_bad, file_info_times, file_write_file_info, + file_info_bad, file_info_times, + file_handle_info_basic_file, file_handle_info_basic_directory, + file_handle_info_times, + file_write_file_info, file_wfi_helpers]}, {consult, [], [consult1, path_consult]}, {eval, [], [eval1, path_eval]}, @@ -1417,7 +1422,8 @@ file_info_basic_directory(Config) when is_list(Config) -> {win32, _} -> test_directory("/", read_write), test_directory("c:/", read_write), - test_directory("c:\\", read_write); + test_directory("c:\\", read_write), + test_directory("\\\\localhost\\c$", read_write); _ -> test_directory("/", read) end, @@ -1549,6 +1555,180 @@ filter_atime(Atime, Config) -> Atime end. +%% Test read_file_info on I/O devices. + +file_handle_info_basic_file(Config) when is_list(Config) -> + RootDir = proplists:get_value(priv_dir, Config), + + %% Create a short file. + Name = filename:join(RootDir, + atom_to_list(?MODULE) + ++"_basic_test.fil"), + {ok,Fd1} = ?FILE_MODULE:open(Name, write), + io:put_chars(Fd1, "foo bar"), + ok = ?FILE_MODULE:close(Fd1), + + %% Test that the file has the expected attributes. + %% The times are tricky, so we will save them to a separate test case. + + {ok, Fd} = ?FILE_MODULE:open(Name, read), + {ok,FileInfo} = ?FILE_MODULE:read_file_info(Fd), + ok = ?FILE_MODULE:close(Fd), + + {ok, FdRaw} = ?FILE_MODULE:open(Name, [read, raw]), + {ok,FileInfoRaw} = ?FILE_MODULE:read_file_info(FdRaw), + ok = ?FILE_MODULE:close(FdRaw), + + #file_info{size=Size,type=Type,access=Access, + atime=AccessTime,mtime=ModifyTime} = FileInfo = FileInfoRaw, + io:format("Access ~p, Modify ~p", [AccessTime, ModifyTime]), + Size = 7, + Type = regular, + read_write = Access, + true = abs(time_dist(filter_atime(AccessTime, Config), + filter_atime(ModifyTime, + Config))) < 5, + all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)), + + [] = flush(), + ok. + +file_handle_info_basic_directory(Config) when is_list(Config) -> + %% Note: filename:join/1 removes any trailing slash, + %% which is essential for ?FILE_MODULE:file_info/1 to work on + %% platforms such as Windows95. + RootDir = filename:join([proplists:get_value(priv_dir, Config)]), + + %% Test that the RootDir directory has the expected attributes. + test_directory_handle(RootDir, read_write), + + %% Note that on Windows file systems, + %% "/" or "c:/" are *NOT* directories. + %% Therefore, test that ?FILE_MODULE:file_info/1 behaves as if they were + %% directories. + case os:type() of + {win32, _} -> + test_directory_handle("/", read_write), + test_directory_handle("c:/", read_write), + test_directory_handle("c:\\", read_write), + test_directory_handle("\\\\localhost\\c$", read_write); + _ -> + test_directory_handle("/", read) + end, + ok. + +test_directory_handle(Name, ExpectedAccess) -> + {ok, DirFd} = file:open(Name, [read, directory]), + try + {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd), + {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd, [raw]), + #file_info{size=Size,type=Type,access=Access, + atime=AccessTime,mtime=ModifyTime} = FileInfo, + io:format("Testing directory ~s", [Name]), + io:format("Directory size is ~p", [Size]), + io:format("Access ~p", [Access]), + io:format("Access time ~p; Modify time~p", + [AccessTime, ModifyTime]), + Type = directory, + Access = ExpectedAccess, + all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)), + [] = flush(), + ok + after + file:close(DirFd) + end. + +%% Test that the file times behave as they should. + +file_handle_info_times(Config) when is_list(Config) -> + %% We have to try this twice, since if the test runs across the change + %% of a month the time diff calculations will fail. But it won't happen + %% if you run it twice in succession. + test_server:m_out_of_n( + 1,2, + fun() -> file_handle_info_int(Config) end), + ok. + +file_handle_info_int(Config) -> + %% Note: filename:join/1 removes any trailing slash, + %% which is essential for ?FILE_MODULE:file_info/1 to work on + %% platforms such as Windows95. + + RootDir = filename:join([proplists:get_value(priv_dir, Config)]), + io:format("RootDir = ~p", [RootDir]), + + Name = filename:join(RootDir, + atom_to_list(?MODULE) + ++"_file_info.fil"), + {ok,Fd1} = ?FILE_MODULE:open(Name, write), + io:put_chars(Fd1,"foo"), + {ok,FileInfo1} = ?FILE_MODULE:read_file_info(Fd1), + ok = ?FILE_MODULE:close(Fd1), + + {ok,Fd1Raw} = ?FILE_MODULE:open(Name, [read, raw]), + {ok,FileInfo1Raw} = ?FILE_MODULE:read_file_info(Fd1Raw), + ok = ?FILE_MODULE:close(Fd1Raw), + + %% We assert that everything but the size is the same, on some OSs the + %% size may not have been flushed to disc and we do not want to do a + %% sync to force it. + FileInfo1Raw = FileInfo1#file_info{ size = FileInfo1Raw#file_info.size }, + + #file_info{type=regular,atime=AccTime1,mtime=ModTime1} = FileInfo1, + + Now = erlang:localtime(), %??? + io:format("Now ~p",[Now]), + io:format("Open file Acc ~p Mod ~p",[AccTime1,ModTime1]), + true = abs(time_dist(filter_atime(Now, Config), + filter_atime(AccTime1, + Config))) < 8, + true = abs(time_dist(Now,ModTime1)) < 8, + + %% Sleep until we can be sure the seconds value has changed. + %% Note: FAT-based filesystem (like on Windows 95) have + %% a resolution of 2 seconds. + timer:sleep(2200), + + %% close the file, and watch the modify date change + + {ok,Fd2} = ?FILE_MODULE:open(Name, read), + {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Fd2), + ok = ?FILE_MODULE:close(Fd2), + + {ok,Fd2Raw} = ?FILE_MODULE:open(Name, [read, raw]), + {ok,FileInfo2Raw} = ?FILE_MODULE:read_file_info(Fd2Raw), + ok = ?FILE_MODULE:close(Fd2Raw), + + #file_info{size=Size,type=regular,access=Access, + atime=AccTime2,mtime=ModTime2} = FileInfo2 = FileInfo2Raw, + io:format("Closed file Acc ~p Mod ~p",[AccTime2,ModTime2]), + true = time_dist(ModTime1,ModTime2) >= 0, + + %% this file is supposed to be binary, so it'd better keep it's size + Size = 3, + Access = read_write, + + %% Do some directory checking + + {ok,Fd3} = ?FILE_MODULE:open(RootDir, [read, directory]), + {ok,FileInfo3} = ?FILE_MODULE:read_file_info(Fd3), + ok = ?FILE_MODULE:close(Fd3), + + {ok,Fd3Raw} = ?FILE_MODULE:open(RootDir, [read, directory, raw]), + {ok,FileInfo3Raw} = ?FILE_MODULE:read_file_info(Fd3Raw), + ok = ?FILE_MODULE:close(Fd3Raw), + + #file_info{size=DSize,type=directory,access=DAccess, + atime=AccTime3,mtime=ModTime3} = FileInfo3 = FileInfo3Raw, + %% this dir was modified only a few secs ago + io:format("Dir Acc ~p; Mod ~p; Now ~p", [AccTime3, ModTime3, Now]), + true = abs(time_dist(Now,ModTime3)) < 5, + DAccess = read_write, + io:format("Dir size is ~p",[DSize]), + + [] = flush(), + ok. + %% Test the write_file_info/2 function. file_write_file_info(Config) when is_list(Config) -> |