%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% %% 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. %% %% %CopyrightEnd% %% %% This is a development feature when developing a new file module, %% ugly but practical. -ifndef(FILE_MODULE). -define(FILE_MODULE, file). -endif. -ifndef(FILE_SUITE). -define(FILE_SUITE, file_SUITE). -endif. -ifndef(FILE_INIT). -define(FILE_INIT(Config), Config). -endif. -ifndef(FILE_FINI). -define(FILE_FINI(Config), Config). -endif. -ifndef(FILE_INIT_PER_TESTCASE). -define(FILE_INIT_PER_TESTCASE(Config), Config). -endif. -ifndef(FILE_FIN_PER_TESTCASE). -define(FILE_FIN_PER_TESTCASE(Config), Config). -endif. -define(PRIM_FILE, prim_file). -module(?FILE_SUITE). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2, read_write_file/1, names/1]). -export([cur_dir_0/1, cur_dir_1/1, make_del_dir/1, list_dir/1,list_dir_error/1, untranslatable_names/1, untranslatable_names_error/1, pos1/1, pos2/1, pos3/1]). -export([close/1, consult1/1, path_consult/1, delete/1]). -export([ eval1/1, path_eval/1, script1/1, path_script/1, open1/1, old_modes/1, new_modes/1, path_open/1, open_errors/1]). -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]). -export([otp_5814/1, otp_10852/1]). -export([ read_not_really_compressed/1, read_compressed_cooked/1, read_compressed_cooked_binary/1, read_cooked_tar_problem/1, write_compressed/1, compress_errors/1, catenated_gzips/1, compress_async_crash/1]). -export([ make_link/1, read_link_info_for_non_link/1, symlinks/1]). -export([copy/1]). -export([new_slave/2, old_slave/2, run_test/2]). -export([delayed_write/1, read_ahead/1, segment_read/1, segment_write/1]). -export([ipread/1]). -export([pid2name/1]). -export([interleaved_read_write/1]). -export([unicode/1]). -export([altname/1]). -export([large_file/0, large_file/1, large_write/0, large_write/1]). -export([read_line_1/1, read_line_2/1, read_line_3/1,read_line_4/1]). -export([advise/1]). -export([allocate/1]). -export([standard_io/1,mini_server/1]). -export([old_io_protocol/1]). -export([unicode_mode/1]). -export([volume_relative_paths/1,unc_paths/1]). -export([tiny_writes/1, tiny_writes_delayed/1, large_writes/1, large_writes_delayed/1, tiny_reads/1, tiny_reads_ahead/1]). %% Debug exports -export([create_file_slow/2, create_file/2, create_bin/2]). -export([verify_file/2, verify_bin/3]). -export([bytes/2, iterate/3]). %% System probe functions that might be handy to check from the shell -export([disc_free/1, memsize/0]). -include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct_event.hrl"). -include_lib("kernel/include/file.hrl"). -define(THROW_ERROR(RES), throw({fail, ?LINE, RES})). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,1}}]. all() -> [unicode, altname, read_write_file, {group, dirs}, {group, files}, delete, rename, names, volume_relative_paths, unc_paths, {group, errors}, {group, compression}, {group, links}, copy, delayed_write, read_ahead, segment_read, segment_write, ipread, pid2name, interleaved_read_write, otp_5814, otp_10852, large_file, large_write, read_line_1, read_line_2, read_line_3, read_line_4, standard_io, old_io_protocol, unicode_mode, {group, bench} ]. groups() -> [{dirs, [], [make_del_dir, cur_dir_0, cur_dir_1, list_dir, list_dir_error, untranslatable_names, untranslatable_names_error]}, {files, [], [{group, open}, {group, pos}, {group, file_info}, {group, consult}, {group, eval}, {group, script}, truncate, sync, datasync, advise, allocate]}, {open, [], [open1, old_modes, new_modes, path_open, close, access, read_write, pread_write, append, open_errors, exclusive]}, {pos, [], [pos1, pos2, pos3]}, {file_info, [], [file_info_basic_file, file_info_basic_directory, 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]}, {script, [], [script1, path_script]}, {errors, [], [e_delete, e_rename, e_make_dir, e_del_dir]}, {compression, [], [read_compressed_cooked, read_compressed_cooked_binary, read_cooked_tar_problem, read_not_really_compressed, write_compressed, compress_errors, catenated_gzips, compress_async_crash]}, {links, [], [make_link, read_link_info_for_non_link, symlinks]}, {bench, [], [tiny_writes, tiny_writes_delayed, large_writes, large_writes_delayed, tiny_reads, tiny_reads_ahead]}]. init_per_group(_GroupName, Config) -> Config. end_per_group(bench, Config) -> ScratchDir = proplists:get_value(priv_dir, Config), file:delete(filename:join(ScratchDir, "benchmark_scratch_file")), Config; end_per_group(_GroupName, Config) -> Config. init_per_suite(Config) when is_list(Config) -> SaslConfig = case application:start(sasl) of {error, {already_started, sasl}} -> []; ok -> [{sasl,started}] end, application:start(os_mon), case os:type() of {win32, _} -> Priv = proplists:get_value(priv_dir, Config), HasAccessTime = case ?FILE_MODULE:read_file_info(Priv) of {ok, #file_info{atime={_, {0, 0, 0}}}} -> %% This is a unfortunately a FAT file system. [no_access_time]; {ok, _} -> [] end, ?FILE_INIT(HasAccessTime++Config++SaslConfig); _ -> ?FILE_INIT(Config++SaslConfig) end. end_per_suite(Config) when is_list(Config) -> case os:type() of {win32, _} -> os:cmd("subst z: /d"); _ -> ok end, application:stop(os_mon), case proplists:get_value(sasl, Config) of started -> application:stop(sasl); _Else -> ok end, ?FILE_FINI(Config). init_per_testcase(_Func, Config) -> %%error_logger:info_msg("~p:~p *****~n", [?MODULE, _Func]), ?FILE_INIT_PER_TESTCASE(Config). end_per_testcase(_Func, Config) -> %% error_logger:info_msg("~p:~p END *****~n", [?MODULE, _Func]), ?FILE_FIN_PER_TESTCASE(Config). %% Matches a term (the last) against alternatives expect(X, _, X) -> X; expect(_, X, X) -> X. expect(X, _, _, X) -> X; expect(_, X, _, X) -> X; expect(_, _, X, X) -> X. expect(X, _, _, _, X) -> X; expect(_, X, _, _, X) -> X; expect(_, _, X, _, X) -> X; expect(_, _, _, X, X) -> X. %% Calculate the time difference time_dist({YY, MM, DD, H, M, S}, DT) -> time_dist({{YY, MM, DD}, {H, M, S}}, DT); time_dist(DT, {YY, MM, DD, H, M, S}) -> time_dist(DT, {{YY, MM, DD}, {H, M, S}}); time_dist({_D1, _T1} = DT1, {_D2, _T2} = DT2) -> calendar:datetime_to_gregorian_seconds(DT2) - calendar:datetime_to_gregorian_seconds(DT1). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% mini_server(Parent) -> receive die -> ok; {io_request,From,To,{put_chars,_Encoding,Data}} -> Parent ! {io_request,From,To,{put_chars,Data}}, From ! {io_reply, To, ok}, mini_server(Parent); {io_request,From,To,{get_chars,'',N}} -> Parent ! {io_request,From,To,{get_chars,'',N}}, From ! {io_reply, To, {ok, lists:duplicate(N,$a)}}, mini_server(Parent); {io_request,From,To,{get_line,''}} -> Parent ! {io_request,From,To,{get_line,''}}, From ! {io_reply, To, {ok, "hej\n"}}, mini_server(Parent) end. %% Test that standard i/o-servers work with file module. standard_io(Config) when is_list(Config) -> %% Really just a smoke test Pid = spawn(?MODULE,mini_server,[self()]), register(mini_server,Pid), ok = file:write(mini_server,<<"hej\n">>), receive {io_request,_,_,{put_chars,<<"hej\n">>}} -> ok after 1000 -> exit(noreply) end, {ok,"aaaaa"} = file:read(mini_server,5), receive {io_request,_,_,{get_chars,'',5}} -> ok after 1000 -> exit(noreply) end, {ok,"hej\n"} = file:read_line(mini_server), receive {io_request,_,_,{get_line,''}} -> ok after 1000 -> exit(noreply) end, OldGL = group_leader(), group_leader(Pid,self()), ok = file:write(standard_io,<<"hej\n">>), group_leader(OldGL,self()), receive {io_request,_,_,{put_chars,<<"hej\n">>}} -> ok after 1000 -> exit(noreply) end, group_leader(Pid,self()), {ok,"aaaaa"} = file:read(standard_io,5), group_leader(OldGL,self()), receive {io_request,_,_,{get_chars,'',5}} -> ok after 1000 -> exit(noreply) end, group_leader(Pid,self()), {ok,"hej\n"} = file:read_line(standard_io), group_leader(OldGL,self()), receive {io_request,_,_,{get_line,''}} -> ok after 1000 -> exit(noreply) end, Pid ! die, receive after 1000 -> ok end. %% Test that the old file IO protocol =< R16B still works. old_io_protocol(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"old_io_protocol.fil"), MyData = "0123456789abcdefghijklmnopqrstuvxyz", ok = ?FILE_MODULE:write_file(Name, MyData), {ok, Fd} = ?FILE_MODULE:open(Name, write), Fd ! {file_request,self(),Fd,truncate}, receive {file_reply,Fd,ok} -> ok end, ok = ?FILE_MODULE:close(Fd), {ok, <<>>} = ?FILE_MODULE:read_file(Name), [] = flush(), ok. unicode_mode(Config) -> Dir = {dir, proplists:get_value(priv_dir,Config)}, OptVariants = [[Dir], [Dir, {encoding, utf8}], [Dir, binary], [Dir, binary, {encoding, utf8}] ], ReadVariants = [{read, fun(Fd) -> um_read(Fd, fun(Fd1) -> file:read(Fd1, 1024) end) end}, {read_line, fun(Fd) -> um_read(Fd, fun(Fd1) -> file:read_line(Fd1) end) end} %%{pread, fun(Fd) -> file:pread(Fd, 0, 1024) end}, %%{preadl, fun(Fd) -> file:pread(Fd, [{0, 1024}]) end}, ], _ = [read_write_0("ASCII: list: Hello World", Read, Opt) || Opt <- OptVariants, Read <- ReadVariants], _ = [read_write_0("LATIN1: list: åäöÅÄÖ", Read, Opt) || Opt <- OptVariants, Read <- ReadVariants], _ = [read_write_0(<<"ASCII: bin: Hello World">>, Read, Opt) || Opt <- OptVariants, Read <- ReadVariants], _ = [read_write_0(<<"LATIN1: bin: åäöÅÄÖ">>, Read, Opt) || Opt <- OptVariants, Read <- ReadVariants], %% These will be double encoded if option is encoding utf-8 _ = [read_write_0(<<"UTF8: bin: Ωß"/utf8>>, Read, Opt) || Opt <- OptVariants, Read <- ReadVariants], %% These should not work (with encoding set to utf-8) %% according to file's documentation _ = [read_write_0("UTF8: list: Ωß", Read, Opt) || Opt <- OptVariants, Read <- ReadVariants], ok. read_write_0(Str, {Func, ReadFun}, Options) -> try Res = read_write_1(Str, ReadFun, Options), io:format("~p: ~ts ~p '~p'~n", [Func, Str, tl(Options), Res]), ok catch {fail, Line, ReadBytes = [_|_]} -> io:format("~p:~p: ~p ERROR: ~w vs~n ~w~n - ~p~n", [?MODULE, Line, Func, Str, ReadBytes, Options]), exit({error, ?LINE}); {fail, Line, ReadBytes} -> io:format("~p:~p: ~p ERROR: ~ts vs~n ~w~n - ~p~n", [?MODULE, Line, Func, Str, ReadBytes, Options]), exit({error, ?LINE}); error:What:Stacktrace -> io:format("~p:??: ~p ERROR: ~p from~n ~w~n ~p~n", [?MODULE, Func, What, Str, Options]), io:format("\t~p~n", [Stacktrace]), exit({error, ?LINE}) end. read_write_1(Str0, ReadFun, [{dir,Dir}|Options]) -> File = um_filename(Str0, Dir, Options), Pre = "line 1\n", Post = "\nlast line\n", Str = case is_list(Str0) andalso lists:max(Str0) > 255 of false -> %% Normal case Use options {ok, FdW} = file:open(File, [write|Options]), IO = [Pre, Str0, Post], ok = file:write(FdW, IO), case is_binary(Str0) of true -> iolist_to_binary(IO); false -> lists:append(IO) end; true -> %% Test unicode lists {ok, FdW} = file:open(File, [write]), Utf8 = unicode:characters_to_binary([Pre, Str0, Post]), file:write(FdW, Utf8), {unicode, Utf8} end, file:close(FdW), {ok, FdR} = file:open(File, [read|Options]), ReadRes = ReadFun(FdR), file:close(FdR), Res = um_check(Str, ReadRes, Options), file:delete(File), Res. um_read(Fd, Fun) -> um_read(Fd, Fun, []). um_read(Fd, Fun, Acc) -> case Fun(Fd) of eof -> case is_binary(hd(Acc)) of true -> {ok, iolist_to_binary(lists:reverse(Acc))}; false -> {ok, lists:append(lists:reverse(Acc))} end; {ok, Data} -> um_read(Fd, Fun, [Data|Acc]); Error -> Error end. um_check(Str, {ok, Str}, _) -> ok; um_check(Bin, {ok, Res}, _Options) when is_binary(Bin), is_list(Res) -> case list_to_binary(Res) of Bin -> ok; _ -> ?THROW_ERROR(Res) end; um_check(Str, {ok, Res}, _Options) when is_list(Str), is_binary(Res) -> case iolist_to_binary(Str) of Res -> ok; _ -> ?THROW_ERROR(Res) end; um_check({unicode, Utf8Bin}, Res, Options) -> um_check_unicode(Utf8Bin, Res, proplists:get_value(binary, Options, false), proplists:get_value(encoding, Options, none)); um_check(_Str, Res, _Options) -> ?THROW_ERROR(Res). um_check_unicode(Utf8Bin, {ok, Utf8Bin}, true, none) -> ok; um_check_unicode(Utf8Bin, {ok, List = [_|_]}, false, none) -> case binary_to_list(Utf8Bin) == List of true -> ok; false -> ?THROW_ERROR(List) end; um_check_unicode(_Utf8Bin, {error, {no_translation, unicode, latin1}}, _, _) -> no_translation; um_check_unicode(_Utf8Bin, Error = {error, _}, _, _Unicode) -> ?THROW_ERROR(Error); um_check_unicode(_Utf8Bin, {ok, _ListOrBin}, _, _UTF8_) -> %% List = if is_binary(ListOrBin) -> unicode:characters_to_list(ListOrBin); %% true -> ListOrBin %% end, %% io:format("In: ~w~n", [binary_to_list(Utf8Bin)]), %% io:format("Ut: ~w~n", [List]), ?THROW_ERROR({shoud_be, no_translation}). um_filename(Bin, Dir, Options) when is_binary(Bin) -> um_filename(binary_to_list(Bin), Dir, Options); um_filename(Str = [_|_], Dir, Options) -> Name = hd(string:lexemes(Str, ":")), Enc = atom_to_list(proplists:get_value(encoding, Options, latin1)), File = case lists:member(binary, Options) of true -> "test_" ++ Name ++ "_bin_enc_" ++ Enc; false -> "test_" ++ Name ++ "_list_enc_" ++ Enc end, filename:join(Dir, File). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% read_write_file(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_read_write_file"), %% Try writing and reading back some term SomeTerm = {"This term",{will,be},[written,$t,$o],1,file,[]}, Bin1 = term_to_binary(SomeTerm), ok = do_read_write_file(Name, Bin1), %% Try a "null" term NullTerm = [], Bin2 = term_to_binary(NullTerm), ok = do_read_write_file(Name, Bin2), %% Try reading a nonexistent file Name2 = filename:join(RootDir, atom_to_list(?MODULE) ++"_nonexistent_file"), {error, enoent} = ?FILE_MODULE:read_file(Name2), {error, enoent} = ?FILE_MODULE:read_file(""), {error, enoent} = ?FILE_MODULE:read_file(''), %% Try writing to a bad filename {error, enoent} = do_read_write_file("", Bin2), %% Try writing something else than a binary {error, badarg} = do_read_write_file(Name, {1,2,3}), {error, badarg} = do_read_write_file(Name, self()), %% Some non-term binaries ok = do_read_write_file(Name, []), %% Write some iolists ok = do_read_write_file(Name, [Bin1,[],[[Bin2]]]), ok = do_read_write_file(Name, ["string",<<"binary">>]), ok = do_read_write_file(Name, "pure string"), [] = flush(), ok. do_read_write_file(Name, Data) -> case ?FILE_MODULE:write_file(Name, Data) of ok -> BinData = iolist_to_binary(Data), {ok,BinData} = ?FILE_MODULE:read_file(Name), ok = ?FILE_MODULE:write_file(Name, Data, []), {ok,BinData} = ?FILE_MODULE:read_file(Name), ok = ?FILE_MODULE:write_file(Name, Data, [raw]), {ok,BinData} = ?FILE_MODULE:read_file(Name), ok; {error,_}=Res -> Res = ?FILE_MODULE:write_file(Name, Data, []), Res = ?FILE_MODULE:write_file(Name, Data, [raw]), Res end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% make_del_dir(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_mk-dir"), ok = ?FILE_MODULE:make_dir(NewDir), {error, eexist} = ?FILE_MODULE:make_dir(NewDir), ok = ?FILE_MODULE:del_dir(NewDir), {error, enoent} = ?FILE_MODULE:del_dir(NewDir), %% Make sure we are not in a directory directly under test_server %% as that would result in eacces errors when trying to delete '..', %% because there are processes having that directory as current. ok = ?FILE_MODULE:make_dir(NewDir), {ok,CurrentDir} = file:get_cwd(), case {os:type(), length(NewDir) >= 260 } of {{win32,_}, true} -> io:format("Skip set_cwd for windows path longer than 260 (MAX_PATH)\n", []), io:format("\nNewDir = ~p\n", [NewDir]); _ -> ok = ?FILE_MODULE:set_cwd(NewDir) end, try %% Check that we get an error when trying to create... %% a deep directory NewDir2 = filename:join(RootDir, atom_to_list(?MODULE) ++"_mk-dir-noexist/foo"), {error, enoent} = ?FILE_MODULE:make_dir(NewDir2), %% a nameless directory {error, enoent} = ?FILE_MODULE:make_dir(""), %% a directory with illegal name {error, badarg} = ?FILE_MODULE:make_dir({1,2,3}), %% a directory with illegal name, even if it's a (bad) list {error, badarg} = ?FILE_MODULE:make_dir([1,2,3,{}]), %% Maybe this isn't an error, exactly, but worth mentioning anyway: %% ok = ?FILE_MODULE:make_dir([$f,$o,$o,0,$b,$a,$r])), %% The above line works, and created a directory "./foo" %% More elegant would maybe have been to fail, or to really create %% a directory, but with a name that incorporates the "bar" part of %% the list, so that [$f,$o,$o,0,$f,$o,$o] wouldn't refer to the same %% dir. But this would slow it down. %% Try deleting some bad directories %% Deleting the parent directory to the current, sounds dangerous, huh? %% Don't worry ;-) the parent directory should never be empty, right? case ?FILE_MODULE:del_dir('..') of {error, eexist} -> ok; {error, eacces} -> ok; %OpenBSD {error, einval} -> ok %FreeBSD end, {error, enoent} = ?FILE_MODULE:del_dir(""), {error, badarg} = ?FILE_MODULE:del_dir([3,2,1,{}]), [] = flush() after ?FILE_MODULE:set_cwd(CurrentDir) end, ok. cur_dir_0(Config) when is_list(Config) -> %% Find out the current dir, and cd to it ;-) {ok,BaseDir} = ?FILE_MODULE:get_cwd(), Dir1 = BaseDir ++ "", %% Check that it's a string ok = ?FILE_MODULE:set_cwd(Dir1), %% Make a new dir, and cd to that RootDir = proplists:get_value(priv_dir,Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_curdir"), ok = ?FILE_MODULE:make_dir(NewDir), case {os:type(), length(NewDir) >= 260} of {{win32,_}, true} -> io:format("Skip set_cwd for windows path longer than 260 (MAX_PATH):\n"), io:format("\nNewDir = ~p\n", [NewDir]); _ -> io:format("cd to ~s",[NewDir]), ok = ?FILE_MODULE:set_cwd(NewDir), %% Create a file in the new current directory, and check that it %% really is created there UncommonName = "uncommon.fil", {ok,Fd} = ?FILE_MODULE:open(UncommonName,read_write), ok = ?FILE_MODULE:close(Fd), {ok,NewDirFiles} = ?FILE_MODULE:list_dir("."), true = lists:member(UncommonName,NewDirFiles), %% Ensure that we get the same result with a trailing slash; the %% APIs used on Windows will choke on them if passed directly. {ok,NewDirFiles} = ?FILE_MODULE:list_dir("./"), %% Delete the directory and return to the old current directory %% and check that the created file isn't there (too!) expect({error, einval}, {error, eacces}, ?FILE_MODULE:del_dir(NewDir)), ?FILE_MODULE:delete(UncommonName), {ok,[]} = ?FILE_MODULE:list_dir("."), ok = ?FILE_MODULE:set_cwd(Dir1), io:format("cd back to ~s",[Dir1]), ok = ?FILE_MODULE:del_dir(NewDir), {error, enoent} = ?FILE_MODULE:set_cwd(NewDir), ok = ?FILE_MODULE:set_cwd(Dir1), io:format("cd back to ~s",[Dir1]), {ok,OldDirFiles} = ?FILE_MODULE:list_dir("."), false = lists:member(UncommonName,OldDirFiles) end, %% Try doing some bad things {error, badarg} = ?FILE_MODULE:set_cwd({foo,bar}), {error, enoent} = ?FILE_MODULE:set_cwd(""), {error, enoent} = ?FILE_MODULE:set_cwd(".......a......"), {ok,BaseDir} = ?FILE_MODULE:get_cwd(), %% Still there? %% On Windows, there should only be slashes, no backslashes, %% in the return value of get_cwd(). %% (The test is harmless on Unix, because filenames usually %% don't contain backslashes.) {ok, BaseDir} = ?FILE_MODULE:get_cwd(), false = lists:member($\\, BaseDir), [] = flush(), ok. %% Tests ?FILE_MODULE:get_cwd/1. cur_dir_1(Config) when is_list(Config) -> case os:type() of {win32, _} -> win_cur_dir_1(Config); _ -> {error, enotsup} = ?FILE_MODULE:get_cwd("d:") end, [] = flush(), ok. win_cur_dir_1(_Config) -> {ok,BaseDir} = ?FILE_MODULE:get_cwd(), %% Get the drive letter from the current directory, %% and try to get current directory for that drive. [CurDrive,$:|_] = BaseDir, {ok,BaseDir} = ?FILE_MODULE:get_cwd([CurDrive,$:]), io:format("BaseDir = ~s\n", [BaseDir]), %% We should error out on non-existent drives. Any reasonable system will %% have at least one. CurDirs = [?FILE_MODULE:get_cwd([Drive,$:]) || Drive <- lists:seq($A, $Z)], lists:member({error,eaccess}, CurDirs), %% Unfortunately, there is no way to move away from the %% current drive as we can't use the "subst" command from %% a SSH connection. We can't test any more. ok. %%% %%% Test list_dir() on a non-existing pathname. %%% list_dir_error(Config) -> Priv = proplists:get_value(priv_dir, Config), NonExisting = filename:join(Priv, "non-existing-dir"), {error,enoent} = ?FILE_MODULE:list_dir(NonExisting), ok. %%% %%% Test list_dir() and list_dir_all(). %%% list_dir(Config) -> RootDir = proplists:get_value(priv_dir, Config), TestDir = filename:join(RootDir, ?MODULE_STRING++"_list_dir"), ?FILE_MODULE:make_dir(TestDir), list_dir_1(TestDir, 42, []). list_dir_1(TestDir, 0, Sorted) -> [ok = ?FILE_MODULE:delete(filename:join(TestDir, F)) || F <- Sorted], ok = ?FILE_MODULE:del_dir(TestDir); list_dir_1(TestDir, Cnt, Sorted0) -> Base = "file" ++ integer_to_list(Cnt), Name = filename:join(TestDir, Base), ok = ?FILE_MODULE:write_file(Name, Base), Sorted = lists:merge([Base], Sorted0), {ok,DirList0} = ?FILE_MODULE:list_dir(TestDir), {ok,DirList1} = ?FILE_MODULE:list_dir_all(TestDir), Sorted = lists:sort(DirList0), Sorted = lists:sort(DirList1), list_dir_1(TestDir, Cnt-1, Sorted). untranslatable_names(Config) -> case no_untranslatable_names() of true -> {skip,"Not a problem on this OS"}; false -> untranslatable_names_1(Config) end. untranslatable_names_1(Config) -> {ok,OldCwd} = file:get_cwd(), PrivDir = proplists:get_value(priv_dir, Config), Dir = filename:join(PrivDir, "untranslatable_names"), ok = file:make_dir(Dir), Node = start_node(untranslatable_names, "+fnu"), try ok = file:set_cwd(Dir), [ok = file:write_file(F, F) || {_,F} <- untranslatable_names()], ExpectedListDir0 = [unicode:characters_to_list(N, utf8) || {utf8,N} <- untranslatable_names()], ExpectedListDir = lists:sort(ExpectedListDir0), io:format("ExpectedListDir: ~p\n", [ExpectedListDir]), ExpectedListDir = call_and_sort(Node, file, list_dir, [Dir]), ExpectedListDirAll0 = [case Enc of utf8 -> unicode:characters_to_list(N, utf8); latin1 -> N end || {Enc,N} <- untranslatable_names()], ExpectedListDirAll = lists:sort(ExpectedListDirAll0), io:format("ExpectedListDirAll: ~p\n", [ExpectedListDirAll]), ExpectedListDirAll = call_and_sort(Node, file, list_dir_all, [Dir]) after catch test_server:stop_node(Node), file:set_cwd(OldCwd), [file:delete(F) || {_,F} <- untranslatable_names()], file:del_dir(Dir) end, ok. untranslatable_names_error(Config) -> case no_untranslatable_names() of true -> {skip,"Not a problem on this OS"}; false -> untranslatable_names_error_1(Config) end. untranslatable_names_error_1(Config) -> {ok,OldCwd} = file:get_cwd(), PrivDir = proplists:get_value(priv_dir, Config), Dir = filename:join(PrivDir, "untranslatable_names_error"), ok = file:make_dir(Dir), Node = start_node(untranslatable_names, "+fnue"), try ok = file:set_cwd(Dir), [ok = file:write_file(F, F) || {_,F} <- untranslatable_names()], ExpectedListDir0 = [unicode:characters_to_list(N, utf8) || {utf8,N} <- untranslatable_names()], ExpectedListDir = lists:sort(ExpectedListDir0), io:format("ExpectedListDir: ~p\n", [ExpectedListDir]), {error,{no_translation,BadFile}} = rpc:call(Node, file, list_dir, [Dir]), true = lists:keymember(BadFile, 2, untranslatable_names()) after catch test_server:stop_node(Node), file:set_cwd(OldCwd), [file:delete(F) || {_,F} <- untranslatable_names()], file:del_dir(Dir) end, ok. untranslatable_names() -> [{utf8,<<"abc">>}, {utf8,<<"def">>}, {utf8,<<"Lagerl",195,182,"f">>}, {utf8,<<195,150,"stra Emterwik">>}, {latin1,<<"M",229,"rbacka">>}, {latin1,<<"V",228,"rmland">>}]. call_and_sort(Node, M, F, A) -> {ok,Res} = rpc:call(Node, M, F, A), lists:sort(Res). no_untranslatable_names() -> case os:type() of {unix,darwin} -> true; {win32,_} -> true; _ -> false end. start_node(Name, Args) -> [_,Host] = string:lexemes(atom_to_list(node()), "@"), ct:log("Trying to start ~w@~s~n", [Name,Host]), case test_server:start_node(Name, peer, [{args,Args}]) of {error,Reason} -> ct:fail(Reason); {ok,Node} -> ct:log("Node ~p started~n", [Node]), Node end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% open1(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_files"), ok = ?FILE_MODULE:make_dir(NewDir), Name = filename:join(NewDir, "foo1.fil"), {ok,Fd1} = ?FILE_MODULE:open(Name,read_write), {ok,Fd2} = ?FILE_MODULE:open(Name,read), Str = "{a,tuple}.\n", io:format(Fd1,Str,[]), {ok,0} = ?FILE_MODULE:position(Fd1,bof), Str = io:get_line(Fd1,''), Str = io:get_line(Fd2,''), ok = ?FILE_MODULE:close(Fd2), {ok,0} = ?FILE_MODULE:position(Fd1,bof), ok = ?FILE_MODULE:truncate(Fd1), eof = io:get_line(Fd1,''), ok = ?FILE_MODULE:close(Fd1), {ok,Fd3} = ?FILE_MODULE:open(Name,read), eof = io:get_line(Fd3,''), ok = ?FILE_MODULE:close(Fd3), [] = flush(), ok. %% Tests all open modes. old_modes(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_old_open_modes"), ok = ?FILE_MODULE:make_dir(NewDir), Name1 = filename:join(NewDir, "foo1.fil"), Marker = "hello, world", %% write {ok, Fd1} = ?FILE_MODULE:open(Name1, write), ok = io:write(Fd1, Marker), ok = io:put_chars(Fd1, ".\n"), ok = ?FILE_MODULE:close(Fd1), %% read {ok, Fd2} = ?FILE_MODULE:open(Name1, read), {ok, Marker} = io:read(Fd2, prompt), ok = ?FILE_MODULE:close(Fd2), %% read_write {ok, Fd3} = ?FILE_MODULE:open(Name1, read_write), {ok, Marker} = io:read(Fd3, prompt), ok = io:write(Fd3, Marker), ok = ?FILE_MODULE:close(Fd3), [] = flush(), ok. new_modes(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_new_open_modes"), ok = ?FILE_MODULE:make_dir(NewDir), Name1 = filename:join(NewDir, "foo1.fil"), Marker = "hello, world", %% write {ok, Fd1} = ?FILE_MODULE:open(Name1, [write]), ok = io:write(Fd1, Marker), ok = io:put_chars(Fd1, ".\n"), ok = ?FILE_MODULE:close(Fd1), %% read {ok, Fd2} = ?FILE_MODULE:open(Name1, [read]), {ok, Marker} = io:read(Fd2, prompt), ok = ?FILE_MODULE:close(Fd2), %% read and write {ok, Fd3} = ?FILE_MODULE:open(Name1, [read, write]), {ok, Marker} = io:read(Fd3, prompt), ok = io:write(Fd3, Marker), ok = ?FILE_MODULE:close(Fd3), %% read by default {ok, Fd4} = ?FILE_MODULE:open(Name1, []), {ok, Marker} = io:read(Fd4, prompt), ok = ?FILE_MODULE:close(Fd4), %% read and binary {ok, Fd5} = ?FILE_MODULE:open(Name1, [read, binary]), {ok, Marker} = io:read(Fd5, prompt), ok = ?FILE_MODULE:close(Fd5), %% read, raw {ok, Fd6} = ?FILE_MODULE:open(Name1, [read, raw]), {ok, [$\[]} = ?FILE_MODULE:read(Fd6, 1), ok = ?FILE_MODULE:close(Fd6), %% write and sync case ?FILE_MODULE:open(Name1, [write, sync]) of {ok, Fd7} -> ok = io:write(Fd7, Marker), ok = io:put_chars(Fd7, ".\n"), ok = ?FILE_MODULE:close(Fd7), {ok, Fd8} = ?FILE_MODULE:open(Name1, [read]), {ok, Marker} = io:read(Fd8, prompt), ok = ?FILE_MODULE:close(Fd8); {error, enotsup} -> %% for platforms that don't support the sync option ok end, % open directory {ok, Fd9} = ?FILE_MODULE:open(NewDir, [directory]), ok = ?FILE_MODULE:close(Fd9), % open raw directory {ok, Fd10} = ?FILE_MODULE:open(NewDir, [raw, directory]), ok = ?FILE_MODULE:close(Fd10), [] = flush(), ok. path_open(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_path_open"), ok = ?FILE_MODULE:make_dir(NewDir), FileName = "path_open.fil", Name = filename:join(RootDir, FileName), {ok,Fd1,_FullName1} = ?FILE_MODULE:path_open( [RootDir, "nosuch1", NewDir],FileName,write), io:format(Fd1,"ABCDEFGH",[]), ok = ?FILE_MODULE:close(Fd1), %% locate it in the last dir {ok,Fd2,_FullName2} = ?FILE_MODULE:path_open( ["nosuch1", NewDir, RootDir],FileName,read), {ok,2} = ?FILE_MODULE:position(Fd2,2), "C" = io:get_chars(Fd2,'',1), ok = ?FILE_MODULE:close(Fd2), %% Try a failing path {error, enoent} = ?FILE_MODULE:path_open( ["nosuch1", NewDir],FileName,read), %% Check that it's found regardless of path, if an absolute name given {ok,Fd3,_FullPath3} = ?FILE_MODULE:path_open( ["nosuch1", NewDir],Name,read), {ok,2} = ?FILE_MODULE:position(Fd3,2), "C" = io:get_chars(Fd3,'',1), ok = ?FILE_MODULE:close(Fd3), [] = flush(), ok. close(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_close.fil"), {ok,Fd1} = ?FILE_MODULE:open(Name,read_write), %% Just closing it is no fun, we did that a million times already %% This is a common error, for code written before Erlang 4.3 %% bacause then ?FILE_MODULE:open just returned a Pid, and not everyone %% really checked what they got. {'EXIT',_Msg} = (catch ok = ?FILE_MODULE:close({ok,Fd1})), ok = ?FILE_MODULE:close(Fd1), %% Try closing one more time Val = ?FILE_MODULE:close(Fd1), io:format("Second close gave: ~p",[Val]), %% All operations on a closed raw file should EINVAL, even if they're not %% supported on the current platform. {ok,Fd2} = ?FILE_MODULE:open(Name, [read, write, raw]), ok = ?FILE_MODULE:close(Fd2), {error, einval} = ?FILE_MODULE:advise(Fd2, 5, 5, normal), {error, einval} = ?FILE_MODULE:allocate(Fd2, 5, 5), {error, einval} = ?FILE_MODULE:close(Fd2), {error, einval} = ?FILE_MODULE:datasync(Fd2), {error, einval} = ?FILE_MODULE:position(Fd2, 5), {error, einval} = ?FILE_MODULE:pread(Fd2, 5, 1), {error, einval} = ?FILE_MODULE:pwrite(Fd2, 5, "einval please"), {error, einval} = ?FILE_MODULE:read(Fd2, 1), {error, einval} = ?FILE_MODULE:sync(Fd2), {error, einval} = ?FILE_MODULE:truncate(Fd2), {error, einval} = ?FILE_MODULE:write(Fd2, "einval please"), [] = flush(), ok. access(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_access.fil"), Str = "ABCDEFGH", {ok,Fd1} = ?FILE_MODULE:open(Name,write), io:format(Fd1,Str,[]), ok = ?FILE_MODULE:close(Fd1), %% Check that we can't write when in read only mode {ok,Fd2} = ?FILE_MODULE:open(Name,read), case catch io:format(Fd2,"XXXX",[]) of ok -> ct:fail({format,write}); _ -> ok end, ok = ?FILE_MODULE:close(Fd2), {ok,Fd3} = ?FILE_MODULE:open(Name,read), Str = io:get_line(Fd3,''), ok = ?FILE_MODULE:close(Fd3), [] = flush(), ok. %% Tests ?FILE_MODULE:read/2 and ?FILE_MODULE:write/2. read_write(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_read_write"), ok = ?FILE_MODULE:make_dir(NewDir), Marker = "hello, world", MarkerB = list_to_binary(Marker), %% Plain file. Name1 = filename:join(NewDir, "plain.fil"), {ok, Fd1} = ?FILE_MODULE:open(Name1, [read, write]), read_write_test(Fd1, Marker, []), %% Raw file. Name2 = filename:join(NewDir, "raw.fil"), {ok, Fd2} = ?FILE_MODULE:open(Name2, [read, write, raw]), read_write_test(Fd2, Marker, []), %% Plain binary file. Name3 = filename:join(NewDir, "plain-b.fil"), {ok, Fd3} = ?FILE_MODULE:open(Name3, [read, write, binary]), read_write_test(Fd3, MarkerB, <<>>), %% Raw binary file. Name4 = filename:join(NewDir, "raw-b.fil"), {ok, Fd4} = ?FILE_MODULE:open(Name4, [read, write, raw, binary]), read_write_test(Fd4, MarkerB, <<>>), ok. read_write_test(File, Marker, Empty) -> ok = ?FILE_MODULE:write(File, Marker), {ok, 0} = ?FILE_MODULE:position(File, 0), {ok, Empty} = ?FILE_MODULE:read(File, 0), {ok, Marker} = ?FILE_MODULE:read(File, 100), eof = ?FILE_MODULE:read(File, 100), {ok, Empty} = ?FILE_MODULE:read(File, 0), ok = ?FILE_MODULE:close(File), [] = flush(), ok. %% Tests ?FILE_MODULE:pread/2 and ?FILE_MODULE:pwrite/2. pread_write(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_pread_write"), ok = ?FILE_MODULE:make_dir(NewDir), List = "hello, world", Bin = list_to_binary(List), %% Plain file. Name1 = filename:join(NewDir, "plain.fil"), {ok, Fd1} = ?FILE_MODULE:open(Name1, [read, write]), pread_write_test(Fd1, List), %% Raw file. Name2 = filename:join(NewDir, "raw.fil"), {ok, Fd2} = ?FILE_MODULE:open(Name2, [read, write, raw]), pread_write_test(Fd2, List), %% Plain file. Binary mode. Name3 = filename:join(NewDir, "plain-binary.fil"), {ok, Fd3} = ?FILE_MODULE:open(Name3, [binary, read, write]), pread_write_test(Fd3, Bin), %% Raw file. Binary mode. Name4 = filename:join(NewDir, "raw-binary.fil"), {ok, Fd4} = ?FILE_MODULE:open(Name4, [binary, read, write, raw]), pread_write_test(Fd4, Bin), ok. pread_write_test(File, Data) -> io:format("~p:pread_write_test(~p,~p)~n", [?MODULE, File, Data]), Size = if is_binary(Data) -> byte_size(Data); is_list(Data) -> length(Data) end, I = Size + 17, ok = ?FILE_MODULE:pwrite(File, 0, Data), {ok, Data} = ?FILE_MODULE:pread(File, 0, I), {ok, [Data]} = ?FILE_MODULE:pread(File, [{0, I}]), eof = ?FILE_MODULE:pread(File, I, 1), ok = ?FILE_MODULE:pwrite(File, [{0, Data}, {I, Data}]), {ok, [Data, eof, Data]} = ?FILE_MODULE:pread(File, [{0, Size}, {2*I, 1}, {I, Size}]), Plist = lists:seq(21*I, 0, -I), Pwrite = lists:map(fun(P)->{P,Data}end, Plist), Pread = [{22*I,Size} | lists:map(fun(P)->{P,Size}end, Plist)], Presult = [eof | lists:map(fun(_)->Data end, Plist)], ok = ?FILE_MODULE:pwrite(File, Pwrite), {ok, Presult} = ?FILE_MODULE:pread(File, Pread), ok = ?FILE_MODULE:close(File), [] = flush(), ok. %% Test appending to a file. append(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_append"), ok = ?FILE_MODULE:make_dir(NewDir), First = "First line\n", Second = "Seond lines comes here\n", Third = "And here is the third line\n", %% Write a small text file. Name1 = filename:join(NewDir, "a_file.txt"), {ok, Fd1} = ?FILE_MODULE:open(Name1, [write]), ok = io:format(Fd1, First, []), ok = io:format(Fd1, Second, []), ok = ?FILE_MODULE:close(Fd1), %% Open it a again and a append a line to it. {ok, Fd2} = ?FILE_MODULE:open(Name1, [append]), ok = io:format(Fd2, Third, []), ok = ?FILE_MODULE:close(Fd2), %% Read it back and verify. Expected = list_to_binary([First, Second, Third]), {ok, Expected} = ?FILE_MODULE:read_file(Name1), [] = flush(), ok. open_errors(Config) when is_list(Config) -> DataDir = filename:dirname( filename:join(proplists:get_value(data_dir, Config), "x")), DataDirSlash = DataDir++"/", {error, E1} = ?FILE_MODULE:open(DataDir, [read]), {error, E2} = ?FILE_MODULE:open(DataDirSlash, [read]), {error, E3} = ?FILE_MODULE:open(DataDir, [write]), {error, E4} = ?FILE_MODULE:open(DataDirSlash, [write]), {eisdir,eisdir,eisdir,eisdir} = {E1,E2,E3,E4}, Real = filename:join(DataDir, "realmen.html"), {error, enotdir} = ?FILE_MODULE:open(Real, [directory]), [] = flush(), ok. %% Test exclusive access to a file. exclusive(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), NewDir = filename:join(RootDir, atom_to_list(?MODULE) ++"_exclusive"), ok = ?FILE_MODULE:make_dir(NewDir), Name = filename:join(NewDir, "ex_file.txt"), {ok, Fd} = ?FILE_MODULE:open(Name, [write, exclusive]), {error, eexist} = ?FILE_MODULE:open(Name, [write, exclusive]), ok = ?FILE_MODULE:close(Fd), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% pos1(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_pos1.fil"), {ok,Fd1} = ?FILE_MODULE:open(Name,write), io:format(Fd1,"ABCDEFGH",[]), ok = ?FILE_MODULE:close(Fd1), {ok,Fd2} = ?FILE_MODULE:open(Name,read), %% Start pos is first char io:format("Relative positions"), "A" = io:get_chars(Fd2,'',1), {ok,2} = ?FILE_MODULE:position(Fd2,{cur,1}), "C" = io:get_chars(Fd2,'',1), {ok,0} = ?FILE_MODULE:position(Fd2,{cur,-3}), "A" = io:get_chars(Fd2,'',1), %% Backwards from first char should be an error {ok,0} = ?FILE_MODULE:position(Fd2,{cur,-1}), {error, einval} = ?FILE_MODULE:position(Fd2,{cur,-1}), %% Reset position and move again {ok,0} = ?FILE_MODULE:position(Fd2,0), {ok,2} = ?FILE_MODULE:position(Fd2,{cur,2}), "C" = io:get_chars(Fd2,'',1), %% Go a lot forwards {ok,13} = ?FILE_MODULE:position(Fd2,{cur,10}), eof = io:get_chars(Fd2,'',1), %% Try some fixed positions io:format("Fixed positions"), {ok,8} = ?FILE_MODULE:position(Fd2,8), eof = io:get_chars(Fd2,'',1), {ok,8} = ?FILE_MODULE:position(Fd2,cur), eof = io:get_chars(Fd2,'',1), {ok,7} = ?FILE_MODULE:position(Fd2,7), "H" = io:get_chars(Fd2,'',1), {ok,0} = ?FILE_MODULE:position(Fd2,0), "A" = io:get_chars(Fd2,'',1), {ok,3} = ?FILE_MODULE:position(Fd2,3), "D" = io:get_chars(Fd2,'',1), {ok,12} = ?FILE_MODULE:position(Fd2,12), eof = io:get_chars(Fd2,'',1), {ok,3} = ?FILE_MODULE:position(Fd2,3), "D" = io:get_chars(Fd2,'',1), %% Try the {bof,X} notation {ok,3} = ?FILE_MODULE:position(Fd2,{bof,3}), "D" = io:get_chars(Fd2,'',1), %% Try eof positions io:format("EOF positions"), {ok,8} = ?FILE_MODULE:position(Fd2,{eof,0}), eof=io:get_chars(Fd2,'',1), {ok,7} = ?FILE_MODULE:position(Fd2,{eof,-1}), "H" = io:get_chars(Fd2,'',1), {ok,0} = ?FILE_MODULE:position(Fd2,{eof,-8}), "A"=io:get_chars(Fd2,'',1), {error, einval} = ?FILE_MODULE:position(Fd2,{eof,-9}), [] = flush(), ok. pos2(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_pos2.fil"), {ok,Fd1} = ?FILE_MODULE:open(Name,write), io:format(Fd1,"ABCDEFGH",[]), ok = ?FILE_MODULE:close(Fd1), {ok,Fd2} = ?FILE_MODULE:open(Name,read), {error, einval} = ?FILE_MODULE:position(Fd2,-1), %% Make sure that we still can search after an error. {ok,0} = ?FILE_MODULE:position(Fd2, 0), {ok,3} = ?FILE_MODULE:position(Fd2, {bof,3}), "D" = io:get_chars(Fd2,'',1), [] = flush(), io:format("DONE"), ok. %% When it does not use raw mode, file:position had a bug. pos3(Config) when is_list(Config) -> RootDir = proplists:get_value(data_dir, Config), Name = filename:join(RootDir, "realmen.html.gz"), {ok, Fd} = ?FILE_MODULE:open(Name, [read, binary]), {ok, _} = ?FILE_MODULE:read(Fd, 5), {error, einval} = ?FILE_MODULE:position(Fd, {bof, -1}), %% Here ok had returned =( {error, einval} = ?FILE_MODULE:position(Fd, {cur, -10}), %% That test is actually questionable since file:position/2 %% is documented to leave the file position undefined after %% it has returned an error. But on Posix systems the position %% is guaranteed to be unchanged after an error return. On e.g %% Windows there is nothing stated about this in the documentation. ok. file_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), %% Don't crash the file server when passing incorrect arguments. {error,badarg} = ?FILE_MODULE:read_file_info(Name, [{time, gurka}]), {error,badarg} = ?FILE_MODULE:read_file_info([#{} | gaffel]), %% Test that the file has the expected attributes. %% The times are tricky, so we will save them to a separate test case. {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name), {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name, [raw]), #file_info{size=Size,type=Type,access=Access, atime=AccessTime,mtime=ModifyTime} = FileInfo, 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))) < 2, all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)), [] = flush(), ok. file_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(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("/", read_write), test_directory("c:/", read_write), test_directory("c:\\", read_write), test_directory("\\\\localhost\\c$", read_write); _ -> test_directory("/", read) end, ok. test_directory(Name, ExpectedAccess) -> {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name), {ok,FileInfo} = ?FILE_MODULE:read_file_info(Name, [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. all_integers([{A,B,C}|T]) -> all_integers([A,B,C|T]); all_integers([Int|Rest]) when is_integer(Int) -> all_integers(Rest); all_integers([]) -> ok. %% Try something nonexistent. file_info_bad(Config) when is_list(Config) -> RootDir = filename:join([proplists:get_value(priv_dir, Config)]), FileName = filename:join(RootDir, atom_to_list(?MODULE) ++ "_nonexistent"), {error,enoent} = ?FILE_MODULE:read_file_info(FileName), {error,enoent} = ?FILE_MODULE:read_file_info(FileName, [raw]), {error, enoent} = ?FILE_MODULE:read_file_info(""), {error, enoent} = ?FILE_MODULE:read_file_info("", [raw]), [] = flush(), ok. %% Test that the file times behave as they should. file_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_info_int(Config) end), ok. file_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"), %% check that the file got a modify date max a few seconds away from now {ok,FileInfo1} = ?FILE_MODULE:read_file_info(Name), {ok,FileInfo1Raw} = ?FILE_MODULE:read_file_info(Name, [raw]), %% 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 = ?FILE_MODULE:close(Fd1), {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Name), {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Name, [raw]), #file_info{size=Size,type=regular,access=Access, atime=AccTime2,mtime=ModTime2} = FileInfo2, 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,FileInfo3} = ?FILE_MODULE:read_file_info(RootDir), {ok,FileInfo3} = ?FILE_MODULE:read_file_info(RootDir, [raw]), #file_info{size=DSize,type=directory,access=DAccess, atime=AccTime3,mtime=ModTime3} = FileInfo3, %% 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. %% Filter access times, to copy with a deficiency of FAT file systems %% (on Windows): The access time is actually only a date. filter_atime(Atime, Config) -> case lists:member(no_access_time, Config) of true -> case Atime of {Date, _} -> {Date, {0, 0, 0}}; {Y, M, D, _, _, _} -> {Y, M, D, 0, 0, 0} end; false -> 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) -> RootDir = get_good_directory(Config), io:format("RootDir = ~p", [RootDir]), %% Set the file to read only AND update the file times at the same time. %% (This used to fail on Windows NT/95 for a local filesystem.) %% Note: Seconds must be even; see note in file_info_times/1. Name1 = filename:join(RootDir, atom_to_list(?MODULE) ++"_write_file_info_ro"), ok = ?FILE_MODULE:write_file(Name1, "hello"), Time = {{1997, 01, 02}, {12, 35, 42}}, Info = #file_info{mode=8#400, atime=Time, mtime=Time, ctime=Time}, ok = ?FILE_MODULE:write_file_info(Name1, Info), %% Read back the times. {ok, ActualInfo} = ?FILE_MODULE:read_file_info(Name1), #file_info{mode=_Mode, atime=ActAtime, mtime=Time, ctime=ActCtime} = ActualInfo, FilteredAtime = filter_atime(Time, Config), FilteredAtime = filter_atime(ActAtime, Config), case os:type() of {win32, _} -> %% On Windows, "ctime" means creation time and it can %% be set. ActCtime = Time; _ -> ok end, {error, eacces} = ?FILE_MODULE:write_file(Name1, "hello again"), %% Make the file writable again. ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#600}), ok = ?FILE_MODULE:write_file(Name1, "hello again"), %% And unwritable. ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#400}), {error, eacces} = ?FILE_MODULE:write_file(Name1, "hello again"), %% Same with raw. ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#600}, [raw]), ok = ?FILE_MODULE:write_file(Name1, "hello again"), ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#400}, [raw]), {error,eacces} = ?FILE_MODULE:write_file(Name1, "hello again"), %% Write the times again. %% Note: Seconds must be even; see note in file_info_times/1. NewTime = {{1997, 02, 15}, {13, 18, 20}}, NewInfo = #file_info{atime=NewTime, mtime=NewTime, ctime=NewTime}, ok = ?FILE_MODULE:write_file_info(Name1, NewInfo), {ok, ActualInfo2} = ?FILE_MODULE:read_file_info(Name1), #file_info{atime=NewActAtime, mtime=NewTime, ctime=NewActCtime} = ActualInfo2, NewFilteredAtime = filter_atime(NewTime, Config), NewFilteredAtime = filter_atime(NewActAtime, Config), case os:type() of {win32, _} -> NewActCtime = NewTime; _ -> ok end, %% The file should still be unwritable. {error, eacces} = ?FILE_MODULE:write_file(Name1, "hello again"), %% Make the file writeable again, so that we can remove the %% test suites ... :-) ?FILE_MODULE:write_file_info(Name1, #file_info{mode=8#600}), [] = flush(), ok. file_wfi_helpers(Config) when is_list(Config) -> RootDir = get_good_directory(Config), io:format("RootDir = ~p", [RootDir]), Name = filename:join(RootDir, atom_to_list(?MODULE) ++ "_wfi_helpers"), ok = ?FILE_MODULE:write_file(Name, "hello again"), NewTime = {{1997, 02, 15}, {13, 18, 20}}, ok = ?FILE_MODULE:change_time(Name, NewTime, NewTime), {ok, #file_info{atime=NewActAtime, mtime=NewTime}} = ?FILE_MODULE:read_file_info(Name), NewFilteredAtime = filter_atime(NewTime, Config), NewFilteredAtime = filter_atime(NewActAtime, Config), %% Make the file unwritable ok = ?FILE_MODULE:change_mode(Name, 8#400), {error, eacces} = ?FILE_MODULE:write_file(Name, "hello again"), %% ... and writable again ok = ?FILE_MODULE:change_mode(Name, 8#600), ok = ?FILE_MODULE:write_file(Name, "hello again"), %% We have no idea which users will work, so all we can do is to check %% that it returns enoent instead of crashing. {error, enoent} = ?FILE_MODULE:change_group("bogus file name", 0), {error, enoent} = ?FILE_MODULE:change_owner("bogus file name", 0), [] = flush(), ok. %% Returns a directory on a file system that has correct file times. get_good_directory(Config) -> proplists:get_value(priv_dir, Config). consult1(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_consult.fil"), {ok,Fd1} = ?FILE_MODULE:open(Name,write), %% note that there is no final \n (only a space) io:format(Fd1, "{this,[is,1.0],'journey'}.\n\"into\". (sound). ", []), ok = ?FILE_MODULE:close(Fd1), {ok,[{this,[is,1.0],journey},"into",sound]} = ?FILE_MODULE:consult(Name), {ok,Fd2} = ?FILE_MODULE:open(Name,write), %% note the missing double quote io:format( Fd2,"{this,[is,1.0],'journey'}.\n \"into. (sound). ",[]), ok = ?FILE_MODULE:close(Fd2), {error, {_, _, _} = Msg} = ?FILE_MODULE:consult(Name), io:format("Errmsg: ~p",[Msg]), {error, enoent} = ?FILE_MODULE:consult(Name ++ ".nonexistent"), [] = flush(), ok. path_consult(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), FileName = atom_to_list(?MODULE)++"_path_consult.fil", Name = filename:join(RootDir, FileName), {ok,Fd1} = ?FILE_MODULE:open(Name,write), io:format(Fd1,"{this,is,a,journey,into,sound}.\n",[]), ok = ?FILE_MODULE:close(Fd1), %% File last in path {ok,[{this,is,a,journey,into,sound}],Dir} = ?FILE_MODULE:path_consult( [filename:join(RootDir, "dir1"), filename:join(RootDir, ".."), filename:join(RootDir, "dir2"), RootDir], FileName), true = lists:prefix(RootDir,Dir), %% While maybe not an error, it may be worth noting that %% when the full path to a file is given, it's always found %% regardless of the contents of the path {ok,_,_} = ?FILE_MODULE:path_consult(["nosuch1","nosuch2"],Name), [] = flush(), ok. eval1(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE)++"_eval.fil"), {ok,Fd1} = ?FILE_MODULE:open(Name,write), %% note that there is no final \n (only a space) io:format(Fd1,"put(evaluated_ok,\ntrue). ",[]), ok = ?FILE_MODULE:close(Fd1), ok = ?FILE_MODULE:eval(Name), true = get(evaluated_ok), {ok,Fd2} = ?FILE_MODULE:open(Name,write), %% note that there is no final \n (only a space) io:format(Fd2,"put(evaluated_ok,\nR). ",[]), ok = ?FILE_MODULE:close(Fd2), ok = ?FILE_MODULE:eval( Name, erl_eval:add_binding('R', true, erl_eval:new_bindings())), true = get(evaluated_ok), {ok,Fd3} = ?FILE_MODULE:open(Name,write), %% garbled io:format(Fd3,"puGARBLED-GARBLED\ntrue). ",[]), ok = ?FILE_MODULE:close(Fd3), {error, {_, _, _} = Msg} = ?FILE_MODULE:eval(Name), io:format("Errmsg1: ~p",[Msg]), {error, enoent} = ?FILE_MODULE:eval(Name ++ ".nonexistent"), [] = flush(), ok. path_eval(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), FileName = atom_to_list(?MODULE)++"_path_eval.fil", Name = filename:join(RootDir, FileName), {ok,Fd1} = ?FILE_MODULE:open(Name,write), io:format(Fd1,"put(evaluated_ok,true).\n",[]), ok = ?FILE_MODULE:close(Fd1), %% File last in path {ok,Dir} = ?FILE_MODULE:path_eval( [filename:join(RootDir, "dir1"), filename:join(RootDir, ".."), filename:join(RootDir, "dir2"), RootDir],FileName), true = get(evaluated_ok), true = lists:prefix(RootDir,Dir), %% While maybe not an error, it may be worth noting that %% when the full path to a file is given, it's always found %% regardless of the contents of the path {ok,Fd2} = ?FILE_MODULE:open(Name,write), io:format(Fd2,"put(evaluated_ok,R).\n",[]), ok = ?FILE_MODULE:close(Fd2), {ok,_} = ?FILE_MODULE:path_eval( ["nosuch1","nosuch2"], Name, erl_eval:add_binding('R', true, erl_eval:new_bindings())), [] = flush(), ok. script1(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE)++"_script.fil"), {ok,Fd1} = ?FILE_MODULE:open(Name,write), %% note that there is no final \n (only a space) io:format(Fd1,"A = 11,\nB = 6,\nA+B. ",[]), ok = ?FILE_MODULE:close(Fd1), {ok,17} = ?FILE_MODULE:script(Name), {ok,Fd2} = ?FILE_MODULE:open(Name,write), %% note that there is no final \n (only a space) io:format(Fd2,"A = 11,\nA+B. ",[]), ok = ?FILE_MODULE:close(Fd2), {ok,17} = ?FILE_MODULE:script( Name, erl_eval:add_binding('B', 6, erl_eval:new_bindings())), {ok,Fd3} = ?FILE_MODULE:open(Name,write), io:format(Fd3,"A = 11,\nB = six,\nA+B. ",[]), ok = ?FILE_MODULE:close(Fd3), {error, {_, _, _} = Msg} = ?FILE_MODULE:script(Name), io:format("Errmsg1: ~p",[Msg]), {error, enoent} = ?FILE_MODULE:script(Name ++ ".nonexistent"), [] = flush(), ok. path_script(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), FileName = atom_to_list(?MODULE)++"_path_script.fil", Name = filename:join(RootDir, FileName), {ok,Fd1} = ?FILE_MODULE:open(Name,write), io:format(Fd1,"A = 11,\nB = 6,\nA+B.\n",[]), ok = ?FILE_MODULE:close(Fd1), %% File last in path {ok, 17, Dir} = ?FILE_MODULE:path_script( [filename:join(RootDir, "dir1"), filename:join(RootDir, ".."), filename:join(RootDir, "dir2"), RootDir],FileName), true = lists:prefix(RootDir,Dir), %% While maybe not an error, it may be worth noting that %% when the full path to a file is given, it's always found %% regardless of the contents of the path {ok,Fd2} = ?FILE_MODULE:open(Name,write), io:format(Fd2,"A = 11,\nA+B.",[]), ok = ?FILE_MODULE:close(Fd2), {ok, 17, Dir} = ?FILE_MODULE:path_script( ["nosuch1","nosuch2"], Name, erl_eval:add_binding('B', 6, erl_eval:new_bindings())), [] = flush(), ok. truncate(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_truncate.fil"), %% Create a file with some data. MyData = "0123456789abcdefghijklmnopqrstuvxyz", ok = ?FILE_MODULE:write_file(Name, MyData), %% Truncate the file to 10 characters. {ok, Fd} = ?FILE_MODULE:open(Name, read_write), {ok, 10} = ?FILE_MODULE:position(Fd, 10), ok = ?FILE_MODULE:truncate(Fd), ok = ?FILE_MODULE:close(Fd), %% Read back the file and check that it has been truncated. Expected = list_to_binary("0123456789"), {ok, Expected} = ?FILE_MODULE:read_file(Name), %% Open the file read only and verify that it is not possible to %% truncate it, OTP-1960 {ok, Fd2} = ?FILE_MODULE:open(Name, read), {ok, 5} = ?FILE_MODULE:position(Fd2, 5), {error, _} = ?FILE_MODULE:truncate(Fd2), [] = flush(), ok. %% Tests that ?FILE_MODULE:datasync/1 at least doesn't crash. datasync(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), Sync = filename:join(PrivDir, atom_to_list(?MODULE) ++"_sync.fil"), %% Raw open. {ok, Fd} = ?FILE_MODULE:open(Sync, [write, raw]), ok = ?FILE_MODULE:datasync(Fd), ok = ?FILE_MODULE:close(Fd), %% Ordinary open. {ok, Fd2} = ?FILE_MODULE:open(Sync, [write]), ok = ?FILE_MODULE:datasync(Fd2), ok = ?FILE_MODULE:close(Fd2), [] = flush(), ok. %% Tests that ?FILE_MODULE:sync/1 at least doesn't crash. sync(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), Sync = filename:join(PrivDir, atom_to_list(?MODULE) ++"_sync.fil"), %% Raw open. {ok, Fd} = ?FILE_MODULE:open(Sync, [write, raw]), ok = ?FILE_MODULE:sync(Fd), ok = ?FILE_MODULE:close(Fd), %% Ordinary open. {ok, Fd2} = ?FILE_MODULE:open(Sync, [write]), ok = ?FILE_MODULE:sync(Fd2), ok = ?FILE_MODULE:close(Fd2), [] = flush(), ok. %% Tests that ?FILE_MODULE:advise/4 at least doesn't crash. advise(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), Advise = filename:join(PrivDir, atom_to_list(?MODULE) ++"_advise.fil"), Line1 = "Hello\n", Line2 = "World!\n", {ok, Fd} = ?FILE_MODULE:open(Advise, [write]), ok = ?FILE_MODULE:advise(Fd, 0, 0, normal), ok = io:format(Fd, "~s", [Line1]), ok = io:format(Fd, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd), {ok, Fd2} = ?FILE_MODULE:open(Advise, [write]), ok = ?FILE_MODULE:advise(Fd2, 0, 0, random), ok = io:format(Fd2, "~s", [Line1]), ok = io:format(Fd2, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd2), {ok, Fd3} = ?FILE_MODULE:open(Advise, [write]), ok = ?FILE_MODULE:advise(Fd3, 0, 0, sequential), ok = io:format(Fd3, "~s", [Line1]), ok = io:format(Fd3, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd3), {ok, Fd4} = ?FILE_MODULE:open(Advise, [write]), ok = ?FILE_MODULE:advise(Fd4, 0, 0, will_need), ok = io:format(Fd4, "~s", [Line1]), ok = io:format(Fd4, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd4), {ok, Fd5} = ?FILE_MODULE:open(Advise, [write]), ok = ?FILE_MODULE:advise(Fd5, 0, 0, dont_need), ok = io:format(Fd5, "~s", [Line1]), ok = io:format(Fd5, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd5), {ok, Fd6} = ?FILE_MODULE:open(Advise, [write]), ok = ?FILE_MODULE:advise(Fd6, 0, 0, no_reuse), ok = io:format(Fd6, "~s", [Line1]), ok = io:format(Fd6, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd6), {ok, Fd7} = ?FILE_MODULE:open(Advise, [write]), {error, einval} = ?FILE_MODULE:advise(Fd7, 0, 0, bad_advise), ok = ?FILE_MODULE:close(Fd7), %% test write without advise, then a read after an advise {ok, Fd8} = ?FILE_MODULE:open(Advise, [write]), ok = io:format(Fd8, "~s", [Line1]), ok = io:format(Fd8, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd8), {ok, Fd9} = ?FILE_MODULE:open(Advise, [read]), Offset = 0, %% same as a 0 length in some implementations Length = length(Line1) + length(Line2), ok = ?FILE_MODULE:advise(Fd9, Offset, Length, sequential), {ok, Line1} = ?FILE_MODULE:read_line(Fd9), {ok, Line2} = ?FILE_MODULE:read_line(Fd9), eof = ?FILE_MODULE:read_line(Fd9), ok = ?FILE_MODULE:close(Fd9), [] = flush(), ok. %% Tests that ?FILE_MODULE:allocate/3 at least doesn't crash. allocate(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), Allocate = filename:join(PrivDir, atom_to_list(?MODULE) ++"_allocate.fil"), Line1 = "Hello\n", Line2 = "World!\n", {ok, Fd} = ?FILE_MODULE:open(Allocate, [write, binary]), allocate_and_assert(Fd, 1, iolist_size([Line1, Line2])), ok = io:format(Fd, "~s", [Line1]), ok = io:format(Fd, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd), {ok, Fd2} = ?FILE_MODULE:open(Allocate, [write, binary]), allocate_and_assert(Fd2, 1, iolist_size(Line1)), ok = io:format(Fd2, "~s", [Line1]), ok = io:format(Fd2, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd2), {ok, Fd3} = ?FILE_MODULE:open(Allocate, [write, binary]), allocate_and_assert(Fd3, 1, iolist_size(Line1) + 1), ok = io:format(Fd3, "~s", [Line1]), ok = io:format(Fd3, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd3), {ok, Fd4} = ?FILE_MODULE:open(Allocate, [write, binary]), allocate_and_assert(Fd4, 1, 4 * iolist_size([Line1, Line2])), ok = io:format(Fd4, "~s", [Line1]), ok = io:format(Fd4, "~s", [Line2]), ok = ?FILE_MODULE:close(Fd4), [] = flush(), ok. allocate_and_assert(Fd, Offset, Length) -> %% Just verify that calls to ?PRIM_FILE:allocate/3 don't crash or have %% any other negative side effect. We can't really asssert against a %% specific return value, because support for file space pre-allocation %% depends on the OS, OS version and underlying filesystem. %% %% The Linux kernel added support for fallocate() in version 2.6.23, %% which currently works only for the ext4, ocfs2, xfs and btrfs file %% systems. posix_fallocate() is available in glibc as of version %% 2.1.94, but it was buggy until glibc version 2.7. %% %% Mac OS X, as of version 10.3, supports the fcntl operation F_PREALLOCATE. %% %% Solaris supports posix_fallocate() but only for the UFS file system %% apparently (not supported for ZFS). %% %% FreeBSD 9.0 is the first FreeBSD release supporting posix_fallocate(). %% %% For Windows there's apparently no way to pre-allocate file space, at %% least with same semantics as posix_fallocate(), fallocate() and %% fcntl F_PREALLOCATE. Result = ?FILE_MODULE:allocate(Fd, Offset, Length), case os:type() of {win32, _} -> {error, enotsup} = Result; _ -> _ = Result end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% delete(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), Name = filename:join(RootDir, atom_to_list(?MODULE) ++"_delete.fil"), {ok, Fd1} = ?FILE_MODULE:open(Name, write), io:format(Fd1,"ok.\n",[]), ok = ?FILE_MODULE:close(Fd1), %% Check that the file is readable {ok, Fd2} = ?FILE_MODULE:open(Name, read), ok = ?FILE_MODULE:close(Fd2), ok = ?FILE_MODULE:delete(Name), %% Check that the file is not readable anymore {error, _} = ?FILE_MODULE:open(Name, read), %% Try deleting a nonexistent file {error, enoent} = ?FILE_MODULE:delete(Name), [] = flush(), ok. rename(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), FileName1 = atom_to_list(?MODULE)++"_rename.fil", FileName2 = atom_to_list(?MODULE)++"_rename.ful", Name1 = filename:join(RootDir, FileName1), Name2 = filename:join(RootDir, FileName2), {ok,Fd1} = ?FILE_MODULE:open(Name1,write), ok = ?FILE_MODULE:close(Fd1), %% Rename, and check that id really changed name ok = ?FILE_MODULE:rename(Name1,Name2), {error, _} = ?FILE_MODULE:open(Name1,read), {ok,Fd2} = ?FILE_MODULE:open(Name2,read), ok = ?FILE_MODULE:close(Fd2), %% Try renaming something to itself ok = ?FILE_MODULE:rename(Name2,Name2), %% Try renaming something that doesn't exist {error, enoent} = ?FILE_MODULE:rename(Name1,Name2), %% Try renaming to something else than a string {error, badarg} = ?FILE_MODULE:rename(Name1,{foo,bar}), %% Move between directories DirName1 = filename:join(RootDir, atom_to_list(?MODULE) ++"_rename_dir"), DirName2 = filename:join(RootDir, atom_to_list(?MODULE) ++"_second_rename_dir"), Name1foo = filename:join(DirName1, "foo.fil"), Name2foo = filename:join(DirName2, "foo.fil"), Name2bar = filename:join(DirName2, "bar.dir"), ok = ?FILE_MODULE:make_dir(DirName1), %% The name has to include the full file name, path in not enough expect({error, eisdir}, {error, eexist}, ?FILE_MODULE:rename(Name2,DirName1)), ok = ?FILE_MODULE:rename(Name2, Name1foo), %% Now rename the directory ok = ?FILE_MODULE:rename(DirName1,DirName2), %% And check that the file is there now {ok,Fd3} = ?FILE_MODULE:open(Name2foo, read), ok = ?FILE_MODULE:close(Fd3), %% Try some dirty things now: move the directory into itself {error, Msg1} = ?FILE_MODULE:rename(DirName2, Name2bar), io:format("Errmsg1: ~p",[Msg1]), %% move dir into a file in itself {error, Msg2} = ?FILE_MODULE:rename(DirName2, Name2foo), io:format("Errmsg2: ~p",[Msg2]), [] = flush(), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% names(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir,Config), FileName = "foo1.fil", Name1 = filename:join(RootDir, FileName), Name2 = [RootDir,"/","foo1",".","fil"], Name3 = [RootDir,"/",foo,$1,[[[],[],'.']],"f",il], {ok,Fd0} = ?FILE_MODULE:open(Name1,write), ok = ?FILE_MODULE:close(Fd0), %% Try some file names {ok,Fd1} = ?FILE_MODULE:open(Name1,read), ok = ?FILE_MODULE:close(Fd1), {ok,Fd2f} = ?FILE_MODULE:open(lists:flatten(Name2),read), ok = ?FILE_MODULE:close(Fd2f), {ok,Fd2} = ?FILE_MODULE:open(Name2,read), ok = ?FILE_MODULE:close(Fd2), {ok,Fd3} = ?FILE_MODULE:open(Name3,read), ok = ?FILE_MODULE:close(Fd3), %% Now try the same on raw files. {ok,Fd4} = ?FILE_MODULE:open(Name2, [read, raw]), ok = ?FILE_MODULE:close(Fd4), {ok,Fd4f} = ?FILE_MODULE:open(lists:flatten(Name2), [read, raw]), ok = ?FILE_MODULE:close(Fd4f), {ok,Fd5} = ?FILE_MODULE:open(Name3, [read, raw]), ok = ?FILE_MODULE:close(Fd5), case length(Name1) > 255 of true -> io:format("Path too long for an atom:\n\n~p\n", [Name1]); false -> Name4 = list_to_atom(Name1), {ok,Fd6} = ?FILE_MODULE:open(Name4,read), ok = ?FILE_MODULE:close(Fd6) end, %% Try some path names Path1 = RootDir, Path2 = [RootDir], Path3 = ['',[],[RootDir,[[]]]], {ok,Fd11,_} = ?FILE_MODULE:path_open([Path1],FileName,read), ok = ?FILE_MODULE:close(Fd11), {ok,Fd12,_} = ?FILE_MODULE:path_open([Path2],FileName,read), ok = ?FILE_MODULE:close(Fd12), {ok,Fd13,_} = ?FILE_MODULE:path_open([Path3],FileName,read), ok = ?FILE_MODULE:close(Fd13), case length(Path1) > 255 of true-> io:format("Path too long for an atom:\n\n~p\n", [Path1]); false -> Path4 = list_to_atom(Path1), {ok,Fd14,_} = ?FILE_MODULE:path_open([Path4],FileName,read), ok = ?FILE_MODULE:close(Fd14) end, [] = flush(), ok. volume_relative_paths(Config) when is_list(Config) -> case os:type() of {win32, _} -> {ok, [Drive, $: | _]} = file:get_cwd(), %% Relative to current device root. {ok, RootInfo} = file:read_file_info([Drive, $:, $/]), {ok, RootInfo} = file:read_file_info("/"), %% Relative to current device directory. {ok, DirContents} = file:list_dir([Drive, $:]), {ok, DirContents} = file:list_dir("."), [] = flush(), ok; _ -> {skip, "This test is Windows-specific."} end. unc_paths(Config) when is_list(Config) -> case os:type() of {win32, _} -> %% We assume administrative shares are set up and reachable, and we %% settle for testing presence as some of the returned data is %% different. {ok, _} = file:read_file_info("C:\\Windows\\explorer.exe"), {ok, _} = file:read_file_info("\\\\localhost\\c$\\Windows\\explorer.exe"), {ok, Files} = file:list_dir("C:\\Windows\\"), {ok, Files} = file:list_dir("\\\\localhost\\c$\\Windows\\"), {ok, Cwd} = file:get_cwd(), try ok = file:set_cwd("\\\\localhost\\c$\\Windows\\"), {ok, _} = file:read_file_info("explorer.exe") after file:set_cwd(Cwd) end, [] = flush(), ok; _ -> {skip, "This test is Windows-specific."} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% e_delete(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), Base = filename:join(RootDir, atom_to_list(?MODULE)++"_e_delete"), ok = ?FILE_MODULE:make_dir(Base), %% Delete a non-existing file. {error, enoent} = ?FILE_MODULE:delete(filename:join(Base, "non_existing")), %% Delete a directory. {error, eperm} = ?FILE_MODULE:delete(Base), %% Use a path-name with a non-directory component. Afile = filename:join(Base, "a_file"), ok = ?FILE_MODULE:write_file(Afile, "hello\n"), {error, E} = expect({error, enotdir}, {error, enoent}, ?FILE_MODULE:delete(filename:join(Afile, "another_file"))), io:format("Result: ~p~n", [E]), %% No permission. case os:type() of {win32, _} -> %% Remove a character device. expect({error, eacces}, {error, einval}, ?FILE_MODULE:delete("nul")); _ -> ?FILE_MODULE:write_file_info( Base, #file_info {mode=0}), {error, eacces} = ?FILE_MODULE:delete(Afile), ?FILE_MODULE:write_file_info( Base, #file_info {mode=8#700}) end, [] = flush(), ok. %%% FreeBSD gives EEXIST when renaming a file to an empty dir, although the %%% manual page can be interpreted as saying that EISDIR should be given. %%% (What about FreeBSD? We store our nightly build results on a FreeBSD %%% file system, that's what.) e_rename(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), Base = filename:join(RootDir, atom_to_list(?MODULE)++"_e_rename"), ok = ?FILE_MODULE:make_dir(Base), %% Create an empty directory. EmptyDir = filename:join(Base, "empty_dir"), ok = ?FILE_MODULE:make_dir(EmptyDir), %% Create a non-empty directory. NonEmptyDir = filename:join(Base, "non_empty_dir"), ok = ?FILE_MODULE:make_dir(NonEmptyDir), ok = ?FILE_MODULE:write_file( filename:join(NonEmptyDir, "a_file"), "hello\n"), %% Create another non-empty directory. ADirectory = filename:join(Base, "a_directory"), ok = ?FILE_MODULE:make_dir(ADirectory), ok = ?FILE_MODULE:write_file( filename:join(ADirectory, "a_file"), "howdy\n\n"), %% Create a data file. File = filename:join(Base, "just_a_file"), ok = ?FILE_MODULE:write_file(File, "anything goes\n\n"), %% Move an existing directory to a non-empty directory. {error, eexist} = ?FILE_MODULE:rename(ADirectory, NonEmptyDir), %% Move a root directory. {error, einval} = ?FILE_MODULE:rename("/", "arne"), %% Move Base into Base/new_name. {error, einval} = ?FILE_MODULE:rename(Base, filename:join(Base, "new_name")), %% Overwrite a directory with a file. expect({error, eexist}, %FreeBSD (?) {error, eisdir}, ?FILE_MODULE:rename(File, EmptyDir)), expect({error, eexist}, %FreeBSD (?) {error, eisdir}, ?FILE_MODULE:rename(File, NonEmptyDir)), %% Move a non-existing file. NonExistingFile = filename:join(Base, "non_existing_file"), {error, enoent} = ?FILE_MODULE:rename(NonExistingFile, NonEmptyDir), %% Overwrite a file with a directory. expect({error, eexist}, %FreeBSD (?) {error, enotdir}, ?FILE_MODULE:rename(ADirectory, File)), %% Move a file to another filesystem. %% XXX - This test case is bogus. We cannot be guaranteed that %% the source and destination are on %% different filesystems. %% %% XXX - Gross hack! Comment = case os:type() of {unix, _} -> OtherFs = "/tmp", NameOnOtherFs = filename:join(OtherFs, filename:basename(File)), {ok, Com} = case ?FILE_MODULE:rename(File, NameOnOtherFs) of {error, exdev} -> %% The file could be in %% the same filesystem! {ok, ok}; ok -> {ok, {comment, "Moving between filesystems " "suceeded, files are probably " "in the same filesystem!"}}; {error, eperm} -> {ok, {comment, "SBS! You don't " "have the permission to do " "this test!"}}; Else -> Else end, Com; {win32, _} -> %% At least Windows NT can %% successfully move a file to %% another drive. ok end, [] = flush(), Comment. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% e_make_dir(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), Base = filename:join(RootDir, atom_to_list(?MODULE)++"_e_make_dir"), ok = ?FILE_MODULE:make_dir(Base), %% A component of the path does not exist. {error, enoent} = ?FILE_MODULE:make_dir(filename:join([Base, "a", "b"])), %% Use a path-name with a non-directory component. Afile = filename:join(Base, "a_directory"), ok = ?FILE_MODULE:write_file(Afile, "hello\n"), case ?FILE_MODULE:make_dir( filename:join(Afile, "another_directory")) of {error, enotdir} -> io:format("Result: enotdir"); {error, enoent} -> io:format("Result: enoent") end, %% No permission (on Unix only). case os:type() of {win32, _} -> ok; _ -> ?FILE_MODULE:write_file_info(Base, #file_info {mode=0}), {error, eacces} = ?FILE_MODULE:make_dir(filename:join(Base, "xxxx")), ?FILE_MODULE:write_file_info( Base, #file_info {mode=8#700}) end, ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% e_del_dir(Config) when is_list(Config) -> RootDir = proplists:get_value(priv_dir, Config), Base = test_server:temp_name(filename:join(RootDir, "e_del_dir")), io:format("Base: ~p", [Base]), ok = ?FILE_MODULE:make_dir(Base), %% Delete a non-existent directory. {error, enoent} = ?FILE_MODULE:del_dir(filename:join(Base, "non_existing")), %% Use a path-name with a non-directory component. Afile = filename:join(Base, "a_directory"), ok = ?FILE_MODULE:write_file(Afile, "hello\n"), {error, E1} = expect({error, enotdir}, {error, enoent}, ?FILE_MODULE:del_dir( filename:join(Afile, "another_directory"))), io:format("Result: ~p", [E1]), %% Delete a non-empty directory. {error, E2} = expect({error, enotempty}, {error, eexist}, {error, eacces}, ?FILE_MODULE:del_dir(Base)), io:format("Result: ~p", [E2]), %% Remove the current directory. {error, E3} = expect({error, einval}, {error, eperm}, % Linux and DUX {error, eacces}, {error, ebusy}, ?FILE_MODULE:del_dir(".")), io:format("Result: ~p", [E3]), %% No permission. case os:type() of {win32, _} -> ok; _ -> ADirectory = filename:join(Base, "no_perm"), ok = ?FILE_MODULE:make_dir(ADirectory), ?FILE_MODULE:write_file_info( Base, #file_info {mode=0}), {error, eacces} = ?FILE_MODULE:del_dir(ADirectory), ?FILE_MODULE:write_file_info( Base, #file_info {mode=8#700}) end, [] = flush(), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Trying reading and positioning from a compressed file. read_compressed_cooked(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Real = filename:join(Data, "realmen.html.gz"), {ok, Fd} = ?FILE_MODULE:open(Real, [read,compressed]), try_read_file_list(Fd). read_compressed_cooked_binary(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Real = filename:join(Data, "realmen.html.gz"), {ok, Fd} = ?FILE_MODULE:open(Real, [read,compressed,binary]), try_read_file_binary(Fd). %% Trying reading and positioning from an uncompressed file, %% but with the compressed flag given. read_not_really_compressed(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Priv = proplists:get_value(priv_dir, Config), %% The file realmen.html might have got CRs added (by WinZip). %% Remove them, or the file positions will not be correct. Real = filename:join(Data, "realmen.html"), RealPriv = filename:join(Priv, atom_to_list(?MODULE)++"_realmen.html"), {ok, RealDataBin} = ?FILE_MODULE:read_file(Real), RealData = remove_crs(binary_to_list(RealDataBin), []), ok = ?FILE_MODULE:write_file(RealPriv, RealData), {ok, Fd} = ?FILE_MODULE:open(RealPriv, [read, compressed]), try_read_file_list(Fd). remove_crs([$\r|Rest], Result) -> remove_crs(Rest, Result); remove_crs([C|Rest], Result) -> remove_crs(Rest, [C|Result]); remove_crs([], Result) -> lists:reverse(Result). try_read_file_list(Fd) -> %% Seek to the current position (nothing should happen). {ok, 0} = ?FILE_MODULE:position(Fd, 0), {ok, 0} = ?FILE_MODULE:position(Fd, {cur, 0}), %% Read a few lines from a compressed file. ShouldBe = "