[m-rev.] [PATCH] Improve Mercury's temporary files and directories predicates.

Paul Bone paul at bone.id.au
Wed Apr 13 13:28:03 AEST 2016


 + Add support for retriving the system/user's temporary directory.
 + 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
    security sensative.

    Document the limitations of these calls for the C# and Erlang backends.

configure.ac:
runtime/mercury_conf.h.in:
    Check for mkdtemp(3).
---
 configure.ac              |   4 +-
 library/io.m              | 319 ++++++++++++++++++++++++++++++++++++----------
 runtime/mercury_conf.h.in |   2 +
 3 files changed, 255 insertions(+), 70 deletions(-)

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.
+    %
+:- 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
+    % file. It is the responsibility of the program to delete the file
+    % when it is no longer needed.
+    %
+    % 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.
+    %
+:- pred make_temp(string::in, string::in, string::out, io::di, io::uo)
+    is det.
+
+    % make_temp_directory(DirName, !IO) creates an empty directory whose name
+    % is different from the name of any existing directory.
+    %
+    % On the C# backend this is insecure as the file permissions are not set
+    % and this call does not test for an existing directory.
+    %
+    % This is unimplemented on the Erlang backend.
+    %
+:- pred make_temp_directory(string::out, io::di, io::uo) is det.
+
+    % make_temp_directory(Dir, Prefix, DirName, !IO) creates an empty directory
+    % whose name is different from the name of any existing directory.  The new
+    % directory will reside in the existing directory specified by `Dir' and
+    % will have a prefix using up to the first 5 characters of `Prefix'.
+    % DirName is bound to the name of the new directory. It is the
+    % responsibility of the program to delete the directory when it is no
+    % longer needed.
+    %
+    % The C# backend has the following limitations:
+    %   + It is insecure as as the file permissions are not set and the call
+    %     does not test for an existing directory.
+    %   + Prefix is ignored.
+    %
+    % This is unimplemented on the Erlang backend.
+    %
+:- pred make_temp_directory(string::in, string::in, string::out,
+    io::di, io::uo) is det.
+
+    % temp_directory(DirName, !IO)
+    %
+    % DirName is the name of a directory where applications should put
+    % temporary files.
+    %
+    % This is implementation-dependent.  For current Mercury implementations,
+    % it is determined as follows:
     % 1. For the non-Java back-ends:
     %    - On Microsoft Windows systems, the file will reside in
     %      the current directory if the TMP environment variable
@@ -1327,17 +1384,7 @@
     %    value of this property is typically "/tmp" or "/var/tmp";
     %    on Microsoft Windows systems it is typically "c:\\temp".
     %
-:- 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
-    % file. It is the responsibility of the program to delete the file
-    % when it is no longer needed.
-    %
-:- pred make_temp(string::in, string::in, string::out, io::di, io::uo)
-    is det.
+:- pred temp_directory(string::out, io::di, io::uo) is det.
 
     % remove_file(FileName, Result, !IO) attempts to remove the file
     % `FileName', binding Result to ok/0 if it succeeds, or error/1 if it
@@ -1807,6 +1854,10 @@
     #endif
 ").
 
+:- pragma foreign_decl("C#", "
+using System.IO;
+").
+
 :- pragma foreign_code("C#", "
     // The ML_ prefixes here are not really needed,
     // since the C# code all gets generated inside a class,
@@ -3770,7 +3821,7 @@ check_directory_accessibility_dotnet(_, _, _, Res, !IO) :-
             // isn't read-only doesn't mean we have permission to write to it.
             // The only way to test whether a directory is writable is to
             // write a file to it. The ideal way to do that would be
-            // io.make_temp, but currently the .NET backend version of that
+            // io.make_temp, but currently the C# backend version of that
             // ignores the directory passed to it.
             System.IO.FileAttributes attrs =
                 System.IO.File.GetAttributes(FileName);
@@ -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
@@ -10336,7 +10374,20 @@ io.make_temp(Dir, Prefix, Name, !IO) :-
         throw_io_error(Message)
     ).
 
-%---------------------------------------------------------------------------%
+make_temp_directory(DirName, !IO) :-
+    temp_directory(Dir, !IO),
+    make_temp_directory(Dir, "mtmp", DirName, !IO).
+
+make_temp_directory(Dir, Prefix, DirName, !IO) :-
+    do_make_temp_directory(Dir, Prefix,
+        char_to_string(dir.directory_separator), DirName, Err, Message, !IO),
+    ( if Err = 0 then
+        true
+    else
+        throw_io_error(Message)
+    ).
+
+%-----------------------------------------------------------------------%
 
 :- pred io.do_make_temp(string::in, string::in, string::in,
     string::out, int::out, string::out, io::di, io::uo) is det.
@@ -10467,55 +10518,42 @@ io.make_temp(Dir, Prefix, Name, !IO) :-
     }
 }").
 
-% For the Java implementation, io.make_temp/3 is overwritten directly,
-% since Java is capable of locating the default temp directory itself.
-
-:- pragma foreign_proc("Java",
-    io.do_make_temp(_Dir::in, _Prefix::in, _Sep::in, _FileName::out,
-        _Error::out, _ErrorMessage::out, _IO0::di, _IO::uo),
-    [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe],
+:- pragma foreign_decl("Java", local,
 "
-    // this function should never be called.
-
-    throw new RuntimeException(""io.do_make_temp not implemented"");
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermissions;
 ").
 
-:- pragma foreign_proc("Java",
-    io.make_temp(FileName::out, _IO0::di, _IO::uo),
-    [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
-        may_not_duplicate],
-"
-    try {
-        java.io.File tmpdir = new java.io.File(
-            java.lang.System.getProperty(""java.io.tmpdir""));
-        FileName = java.io.File.createTempFile(""mtmp"", null, tmpdir).
-            getName();
-    } catch (java.lang.Exception e) {
-        io.ML_throw_io_error(e.getMessage());
-        FileName = null;
-    }
-").
+
+% For the Java implementation, io.make_temp/3 is overwritten directly,
+% since Java is capable of locating the default temp directory itself.
 
 :- pragma foreign_proc("Java",
-    io.make_temp(Dir::in, Prefix::in, FileName::out, _IO0::di, _IO::uo),
+    io.do_make_temp(Dir::in, Prefix::in, _Sep::in, FileName::out,
+        Error::out, ErrorMessage::out, _IO0::di, _IO::uo),
     [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
         may_not_duplicate],
 "
     try {
-        if (Prefix.length() < 3) {
-            // File.createTempFile() requires a Prefix which is
-            // at least 3 characters long.
-            Prefix = Prefix + ""tmp"";
-        } else if (Prefix.length() > 5) {
+        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);
         }
-        FileName = java.io.File.createTempFile(Prefix, null,
-            new java.io.File(Dir)).getName();
+        dir_path = Paths.get(Dir);
+        new_file = Files.createTempFile(dir_path, Prefix, null);
+        FileName = new_file.toAbsolutePath().toString();
+        Error = 0;
+        ErrorMessage = """";
     } catch (java.lang.Exception e) {
-        io.ML_throw_io_error(e.getMessage());
-        FileName = null;
+        FileName = """";
+        Error = -1;
+        ErrorMessage = e.toString();
     }
 ").
 
@@ -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 */
+").
+
+:- 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;
+    }
+}").
+
+:- 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.
+    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.
+
+:- pragma foreign_proc("Java",
+    system_temp_dir(Dir::out, OK::out, _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
+        may_not_duplicate],
+"
+    try {
+        Dir = java.lang.System.getProperty(""java.io.tmpdir"");
+        OK = (Dir != null) ? 1 : 0;
+    } catch (Exception e) {
+        Dir = null;
+        OK = 0;
+    }
+").
+
+:- pragma foreign_proc("C#",
+    system_temp_dir(Dir::out, OK::out, _IO0::di, _IO::uo),
+    [will_not_call_mercury, promise_pure, tabled_for_io, thread_safe,
+        may_not_duplicate],
+"
+    try {
+        Dir = System.IO.Path.GetTempPath();
+        OK = (Dir != null) ? 1 : 0;
+    } catch (System.Exception _) {
+        Dir = null;
+        OK = 0;
+    }
+").
+
+system_temp_dir("", 0, !IO).
+
 %---------------------------------------------------------------------------%
 
 :- pragma foreign_decl("C", "
diff --git a/runtime/mercury_conf.h.in b/runtime/mercury_conf.h.in
index d631cdf..343b71d 100644
--- a/runtime/mercury_conf.h.in
+++ b/runtime/mercury_conf.h.in
@@ -296,6 +296,7 @@
 **                              we have the pthread_mutexattr_setpshared()
 **                              function.
 **  MR_HAVE_MKSTEMP             we have the mkstemp() function.
+**  MR_HAVE_MKDTEMP             we have the mkdtemp() function.
 **  MR_HAVE_SETRLIMIT           we have the setrlimit() function.
 **  MR_HAVE_ISNAN               we have the isnan() function.
 **  MR_HAVE_ISNANF              we have the isnanf() function.
@@ -345,6 +346,7 @@
 #undef  MR_HAVE_DUP2
 #undef  MR_HAVE_FILENO
 #undef  MR_HAVE_MKSTEMP
+#undef  MR_HAVE_MKDTEMP
 #undef  MR_HAVE_ISATTY
 #undef  MR_HAVE_GRANTPT
 #undef  MR_HAVE_UNLOCKPT
-- 
2.8.0.rc3



More information about the reviews mailing list