1 // -*- coding: utf-8 -*-
2 // Copyright (C) 2013, 2015, 2017-2019 Laboratoire de Recherche et
3 // Développement de l'Epita (LRDE).
4 //
5 // This file is part of Spot, a model checking library.
6 //
7 // Spot is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 3 of the License, or
10 // (at your option) any later version.
11 //
12 // Spot is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
15 // License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 
20 #include "config.h"
21 #include <spot/misc/tmpfile.hh>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 
27 using namespace std::string_literals;
28 
29 namespace spot
30 {
31   namespace
32   {
33     std::list<temporary_file*> to_clean;
34 
35     static const char*
get_tmpdir()36     get_tmpdir()
37     {
38       const char* res = secure_getenv("SPOT_TMPDIR");
39       if (res)
40         return res;
41       return secure_getenv("TMPDIR");
42     }
43 
44     static int
create_temporary_file(const char * prefix,const char * suffix,char ** name)45     create_temporary_file(const char* prefix,
46                           const char* suffix,
47                           char** name)
48     {
49       static const char* tmpdir = get_tmpdir();
50       static int tmpdirlen = tmpdir ? strlen(tmpdir) : 0;
51 
52       size_t len = strlen(prefix);
53       size_t slen = 0;
54       if (suffix)
55         len += slen = strlen(suffix);
56       char* x = *name = static_cast<char*>(malloc(tmpdirlen + 1 + len + 6 + 1));
57       if (!x)
58         throw std::bad_alloc();
59       if (tmpdir)
60         {
61           x = stpcpy(x, tmpdir);
62           if (x[-1] != '/')
63             *x++ = '/';
64         }
65       x = stpcpy(x, prefix);
66       x = stpcpy(x, "XXXXXX");
67       int fd;
68       if (suffix)
69         {
70           stpcpy(x, suffix);
71           fd = mkstemps(*name, slen);
72         }
73       else
74         {
75           fd = mkstemp(*name);
76         }
77       if (fd < 0)
78         {
79           std::string err = ("failed to create temporary file "s + *name
80                              + ": " + strerror(errno));
81           if (errno == EACCES)
82             {
83               if (tmpdir)
84                 err += ("\nConsider setting the SPOT_TMPDIR environment "
85                         "variable to a writable directory.");
86               else
87                 err += ("\nConsider executing this from a writable "
88                         "directory, or setting\nthe SPOT_TMPDIR environment "
89                         "variable to such a directory.");
90             }
91           else if (tmpdir)
92             {
93               const char* dir =
94                 secure_getenv("SPOT_TMPDIR") ? "SPOT_TMPDIR" : "TMPDIR";
95               err += ("\nNote that the directory comes from the "s
96                       + dir
97                       + " environment variable.");
98             }
99           throw std::runtime_error(err);
100         }
101       return fd;
102     }
103   }
104 
105 
temporary_file(char * name,cleanpos_t cp)106   temporary_file::temporary_file(char* name, cleanpos_t cp)
107     : name_(name), cleanpos_(cp)
108   {
109   }
110 
~temporary_file()111   temporary_file::~temporary_file()
112   {
113     static bool must_unlink = !secure_getenv("SPOT_TMPKEEP");
114     if (must_unlink)
115       unlink(name_);
116     free(name_);
117     to_clean.erase(cleanpos_);
118   }
119 
open_temporary_file(char * name,cleanpos_t cp,int fd)120   open_temporary_file::open_temporary_file(char* name, cleanpos_t cp, int fd)
121     : temporary_file(name, cp), fd_(fd)
122   {
123   }
124 
~open_temporary_file()125   open_temporary_file::~open_temporary_file()
126   {
127     close();
128   }
129 
130   void
close()131   open_temporary_file::close()
132   {
133     if (fd_ < 0)
134       return;
135     if (::close(fd_))
136       throw std::runtime_error("failed to close "s + name_);
137     fd_ = -1;
138   }
139 
140   temporary_file*
create_tmpfile(const char * prefix,const char * suffix)141   create_tmpfile(const char* prefix, const char* suffix)
142   {
143     char* name;
144     int fd = create_temporary_file(prefix, suffix, &name);
145     if (close(fd))
146       throw std::runtime_error("failed to close "s + name);
147     auto cp = to_clean.insert(to_clean.end(), nullptr);
148     *cp = new temporary_file(name, cp);
149     return *cp;
150   }
151 
152   open_temporary_file*
create_open_tmpfile(const char * prefix,const char * suffix)153   create_open_tmpfile(const char* prefix, const char* suffix)
154   {
155     char* name;
156     int fd = create_temporary_file(prefix, suffix, &name);
157     auto cp = to_clean.insert(to_clean.end(), nullptr);
158     open_temporary_file* otf = new open_temporary_file(name, cp, fd);
159     *cp = otf;
160     return otf;
161   }
162 
163   void
cleanup_tmpfiles()164   cleanup_tmpfiles()
165   {
166     while (!to_clean.empty())
167       delete to_clean.front();
168   }
169 }
170