[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