[m-rev.] for review: Improve Mercury's temporary files and directories predicates.
Julien Fischer
jfischer at opturion.com
Wed Apr 13 14:15:32 AEST 2016
Hi Paul,
On Wed, 13 Apr 2016, Paul Bone wrote:
> For review by anyone.
>
> I don't really know C# and have even less knowledge of Erlang. I've not
> implemented these for the Erlang backend and a reviewer may wish to
> scrutinze the C# code in particular.
>
> Thanks.
>
> ---
>
> Improve Mercury's temporary files and directories predicates.
>
> + Add support for retriving the system/user's temporary directory.
s/retriving/retrieving/
> + Add support for creating temporary directories.
>
> library/io.m:
> Add temp_directory/3
>
> Add make_temp_directory/3 and make_temp_directory/5.
>
> make_temp_file/3 and make_temp_file/5 now return the full pathname on
> the Java backends.
>
> On the Java backend make_temp_file/3 and make_temp_file/5 now create
> files chmod 600, which is necessary when the temporary files are
Add "on Unix-like systems" after "chmod 600" there.
> security sensative.
s/sensative/sensitive/
> Document the limitations of these calls for the C# and Erlang backends.
>
> configure.ac:
> runtime/mercury_conf.h.in:
> Check for mkdtemp(3).
>
> NEWS:
> Announce the new predicates.
> diff --git a/configure.ac b/configure.ac
> index d282008..037a6bd 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1316,8 +1316,8 @@ mercury_check_for_functions \
> grantpt unlockpt ptsname tcgetattr tcsetattr ioctl \
> access sleep opendir readdir closedir mkdir symlink readlink \
> gettimeofday setenv putenv _putenv posix_spawn sched_setaffinity \
> - sched_getaffinity sched_getcpu sched_yield mkstemp setrlimit \
> - fma
> + sched_getaffinity sched_getcpu sched_yield mkstemp mkdtemp
> + setrlimit fma
>
> mercury_check_for_stdio_functions \
> snprintf _snprintf vsnprintf _vsnprintf
> diff --git a/library/io.m b/library/io.m
> index b685315..3195532 100644
> --- a/library/io.m
> +++ b/library/io.m
> @@ -1312,8 +1312,65 @@
> % It is the responsibility of the program to delete the file when it is
> % no longer needed.
> %
> - % The file will reside in an implementation-dependent directory.
> - % For current Mercury implementations, it is determined as follows:
> + % The file is placed in the directory returned by temp_directory/3.
> + %
> + % This is insecure on the Erlang backend, file permissions are not set
> + % properly.
... as it does not set file permissions properly.
> + %
> +:- pred make_temp(string::out, io::di, io::uo) is det.
> +
> + % make_temp(Dir, Prefix, Name, !IO) creates an empty file whose
> + % name is different to the name of any existing file. The file will reside
> + % in the directory specified by `Dir' and will have a prefix using up to
> + % the first 5 characters of `Prefix'. Name is bound to the name of the
You've quoted `Dir' and `Prefix', but not Name.
> + % file. It is the responsibility of the program to delete the file
> + % when it is no longer needed.
Why do we phase that way? This might be better:
It is the responsibility of the caller to delete the file when it
is no longer required.
> + %
> + % The C# backend has the following limitations:
> + % + Dir is ignored.
> + % + Prefix is ignored.
> + %
> + % This is insecure on the Erlang backend, file permissions are not set
> + % properly.
Ditto.
...
> @@ -10310,25 +10361,12 @@ io.setenv(Var, Value) :-
>
> %---------------------------------------------------------------------------%
>
> - % We need to do an explicit check of TMPDIR because not all
> - % systems check TMPDIR for us (eg Linux #$%*@&).
> -io.make_temp(Name, !IO) :-
> - Var = ( if dir.use_windows_paths then "TMP" else "TMPDIR" ),
> - io.get_environment_var(Var, Result, !IO),
> - (
> - Result = yes(Dir)
> - ;
> - Result = no,
> - ( if dir.use_windows_paths then
> - Dir = dir.this_directory
> - else
> - Dir = "/tmp"
> - )
> - ),
> - io.make_temp(Dir, "mtmp", Name, !IO).
> +make_temp(Name, !IO) :-
> + temp_directory(Dir, !IO),
> + make_temp(Dir, "mtmp", Name, !IO).
>
> -io.make_temp(Dir, Prefix, Name, !IO) :-
> - io.do_make_temp(Dir, Prefix, char_to_string(dir.directory_separator),
> +make_temp(Dir, Prefix, Name, !IO) :-
> + do_make_temp(Dir, Prefix, char_to_string(dir.directory_separator),
> Name, Err, Message, !IO),
> ( if Err = 0 then
> true
...
> @@ -10591,6 +10629,151 @@ io.make_temp(Dir, Prefix, Name, !IO) :-
> end.
> ").
>
> +%-----------------------------------------------------------------------%
> +
> +:- pred io.do_make_temp_directory(string::in, string::in, string::in,
> + string::out, int::out, string::out, io::di, io::uo) is det.
> +
> +:- pragma foreign_proc("C",
> + io.do_make_temp_directory(Dir::in, Prefix::in, Sep::in, DirName::out,
> + Error::out, ErrorMessage::out, _IO0::di, _IO::uo),
> + [will_not_call_mercury, promise_pure, tabled_for_io,
> + does_not_affect_liveness],
> +"
> +#ifdef MR_HAVE_MKDTEMP
> + int err;
> +
> + DirName = MR_make_string(MR_ALLOC_ID, ""%s%s%.5sXXXXXX"",
> + Dir, Sep, Prefix);
> + DirName = mkdtemp(DirName);
> + if (DirName == NULL) {
> + ML_maybe_make_err_msg(MR_TRUE, errno,
> + ""error creating temporary directory: "", MR_ALLOC_ID,
> + ErrorMessage);
> + Error = -1;
> + } else {
> + ErrorMessage = MR_make_string_const("""");
> + Error = 0;
> + }
> +#else
> + #warning ""Your system does not have mkdtemp""
> + Error = -1;
> + ErrorMessage =
> + MR_make_string_const(""Your system does not have mkdtemp"");
> + DirName = MR_make_string_const("""");
> +#endif /* HAVE_MKDTEMP */
> +").
Since it isn't going to work on some platforms (e.g. native Windows), please
add a predicate have_make_temp_directory/0 that allows the programer to test
whether a working make_temp_directory is available. (See the handling of
float.fma/3, for example.)
(W.r.t Windows, I'm fairly certain it should be possible to implement
equivalent functionality using the Win32 API, but I can't tell you how to do it
off the top of my head.)
> +:- pragma foreign_proc("C#",
> + io.do_make_temp_directory(Dir::in, _Prefix::in, _Sep::in, DirName::out,
> + Error::out, ErrorMessage::out, _IO0::di, _IO::uo),
> + [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
> +"{
> + try {
> + DirName = Path.Combine(Dir, Path.GetRandomFileName());
> + /*
> + * This is not secure:
> + * 1. We cannot set permissions
> + * 2. We cannot atomically test for and create a directory
> + */
> + Directory.CreateDirectory(DirName);
> + Error = 0;
> + ErrorMessage = """";
> + }
> + catch (System.Exception e)
> + {
> + DirName = """";
> + Error = -1;
> + ErrorMessage = e.Message;
> + }
> +}").
Return a bool flag to indicate an error here -- see my comments on
system_temp_directory below.
> +:- pragma foreign_proc("Java",
> + io.do_make_temp_directory(Dir::in, Prefix::in, _Sep::in, DirName::out,
> + Error::out, ErrorMessage::out, _IO0::di, _IO::uo),
> + [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
> + may_not_duplicate],
> +"
> + try {
> + Path dir_path, new_file;
> +
> + if (Prefix.length() > 5) {
> + // The documentation for io.make_temp says that we should only use
> + // the first five characters of Prefix.
> + Prefix = Prefix.substring(0, 5);
> + }
> + dir_path = Paths.get(Dir);
> + new_file = Files.createTempDirectory(dir_path, Prefix);
> + DirName = new_file.toAbsolutePath().toString();
> + Error = 0;
> + ErrorMessage = """";
> + } catch (java.lang.Exception e) {
> + DirName = """";
> + Error = -1;
> + ErrorMessage = e.toString();
> + }
> +").
> +
> +%---------------------------------------------------------------------------%
> +
> +temp_directory(Dir, !IO) :-
> + % If using a Java or C# backend then use their API to get the location of
> + % temporary files.
*the* Java or C# backends
> + system_temp_dir(Dir0, OK, !IO),
> + ( if OK = 1 then
> + Dir = Dir0
> + else
> + % Either this is not a Java or C# grade or the Java or C# backend
> + % couldn't determine the temporary directory.
> + %
> + % We need to do an explicit check of TMPDIR because not all
> + % systems check TMPDIR for us (eg Linux #$%*@&).
> + Var = ( if dir.use_windows_paths then "TMP" else "TMPDIR" ),
> + get_environment_var(Var, Result, !IO),
> + (
> + Result = yes(Dir)
> + ;
> + Result = no,
> + ( if dir.use_windows_paths then
> + Dir = dir.this_directory
> + else
> + Dir = "/tmp"
> + )
> + )
> + ).
> +
> +:- pred system_temp_dir(string::out, int::out, io::di, io::uo) is det.
Use a bool rather than an int for the second argument.
(bool.YES and bool.NO in Java; mr_bool.YES and mr_bool.NO in C#)
Julien.
More information about the reviews
mailing list