Merge branch 'daemon-ext'
[libi2ncommon] / src / tmpfstream_impl.hpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 /** @file
21  * @brief fstream which creates files with mkstemp.
22  *
23  * @note This file contains the template implementation. You only need
24  *       to include it if you use something else than the explicitly
25  *       instantiated and typedefed versions
26  *
27  * @author Gerd v. Egidy
28  *
29  * @copyright © Copyright 2010 by Intra2net AG
30  */
31
32 #ifndef __I2N_TMPFSTREAM_IMPL_HPP__
33 #define __I2N_TMPFSTREAM_IMPL_HPP__
34
35 #include <string>
36 #include <stdio.h>
37
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <unistd.h>
41 #include <errno.h>
42 #include <stdlib.h>
43 #include <fcntl.h>
44
45 #include <tmpfstream.hpp>
46 #include <filefunc.hxx>
47
48
49 namespace I2n
50 {
51
52 /**
53 * @brief opens the tmpfstreamTempl.
54 *
55 * @param tmpnametemplate Path of the temporary file to be opened.
56 *                        The last 6 characters must be XXXXXX and they
57 *                        will be replaced with something random.
58 * @param mode std::ios_base::open_mode to open the file with
59 * @param buffer_size The size of any buffers that need to be allocated
60 * @param buffer_size The size of the putback buffer, relevant only for fstream
61 * @retval true if successful
62 */
63 template< typename Device, typename Tr, typename Alloc >
64 bool tmpfstreamTempl<Device,Tr,Alloc>::open(const std::string& tmpnametemplate, 
65                std::ios_base::open_mode mode,
66                int buffer_size, int pback_size)
67 {
68     if (tmpfstreamTempl<Device,Tr,Alloc>::is_open())
69         tmpfstreamTempl<Device,Tr,Alloc>::close();
70
71     char* chbuf=new char[tmpnametemplate.size()+1];
72     tmpnametemplate.copy(chbuf,tmpnametemplate.size()+1);
73     chbuf[tmpnametemplate.size()]=0;
74
75     // always assume out-mode, otherwise the tmpfstream would be useless
76     int flags=0;
77     if (mode & std::ios_base::in)
78         flags |= O_RDWR;
79     else
80         flags |= O_WRONLY;
81
82     if (mode & std::ios_base::app)
83         flags |= O_APPEND;
84
85     fd=mkostemp(chbuf,flags);
86     tmpfilename=chbuf;
87     delete[] chbuf;
88
89     if (fd==-1)
90         return false;
91
92     boost::iostreams::stream<Device,Tr,Alloc>::open(Device(fd, boost::iostreams::close_handle),
93                                                     buffer_size,pback_size);
94
95     return tmpfstreamTempl<Device,Tr,Alloc>::is_open();
96 }
97
98 /**
99 * @brief Changes permissions (chmod) of the file.
100 *
101 * @param mode the new mode as in chmod
102 * @retval true if successful
103 */
104 template< typename Device, typename Tr, typename Alloc >
105 bool tmpfstreamTempl<Device,Tr,Alloc>::set_file_mode(mode_t mode)
106 {
107     if (!get_tmp_filename().empty() && !is_unlinked())
108         return I2n::chmod(get_tmp_filename(),mode);
109     else
110         return false;
111 }
112
113 /**
114 * @brief Changes the owner of the file (chown).
115 *
116 * @param user the new owner
117 * @param group the new group, if left empty the main group of the user is set
118 * @retval true if successful
119 */
120 template< typename Device, typename Tr, typename Alloc >
121 bool tmpfstreamTempl<Device,Tr,Alloc>::set_owner(const I2n::User& user, const I2n::Group& group)
122 {
123     if (!get_tmp_filename().empty() && !is_unlinked())
124         return I2n::chown(get_tmp_filename(),user,group);
125     else
126         return false;
127 }
128
129 /**
130 * @brief Delete the file.
131
132 * Can be called while the file is still open.
133 *
134 * @retval true if successful
135 */
136 template< typename Device, typename Tr, typename Alloc >
137 bool tmpfstreamTempl<Device,Tr,Alloc>::unlink()
138 {
139     if (!get_tmp_filename().empty())
140     {
141         if (I2n::unlink(get_tmp_filename()))
142         {
143             unlinked=true;
144             return true;
145         }
146         else
147             return false;
148     }
149     else
150         return false;
151 }
152
153 /**
154 * @brief Move the file to another name or path.
155
156 * The temporary file and the target path must be on the same filesystem.
157 * Afterwards all operations (e.g. @ref unlink) are on the new filename.
158 *
159 * @param targetpath name and path of the new filename
160 * @param overwrite overwrite an already existing targetpath or not
161 * @retval true if successful
162 */
163 template< typename Device, typename Tr, typename Alloc >
164 bool tmpfstreamTempl<Device,Tr,Alloc>::move(const std::string& targetpath, 
165                                           bool overwrite)
166 {
167     if (get_tmp_filename().empty() || is_unlinked())
168         return false;
169
170     bool success=false;
171
172     if (overwrite)
173     {
174         // this overwrites an already existing target without further warning
175         // other errors possible, see errno
176         if (::rename( get_tmp_filename().c_str(), targetpath.c_str() ) == 0)
177             success=true;
178     }
179     else
180     {
181         // fails if the target already exists
182         // other errors possible, see errno
183         if (::link( get_tmp_filename().c_str(), targetpath.c_str() ) == 0)
184         {
185             success=true;
186             ::unlink(get_tmp_filename().c_str());
187         }
188     }
189
190     if (success)
191     {
192         // store tmpfilename to allow double-move
193         tmpfilename=targetpath;
194     }
195
196     return success;
197 }
198
199 /**
200 * @brief Sync the data and metadata of the file to disk.
201
202 * @retval true if successful
203 */
204 template< typename Device, typename Tr, typename Alloc >
205 bool tmpfstreamTempl<Device,Tr,Alloc>::sync()
206 {
207     if (!tmpfstreamTempl<Device,Tr,Alloc>::is_open())
208         return false;
209
210     tmpfstreamTempl<Device,Tr,Alloc>::flush();
211
212     // sync the file itself
213     if (fsync(fd) != 0)
214         return false;
215
216     // sync the dir: both is needed for data + metadata sync
217     return dirsync(dirname(get_tmp_filename()));
218 }
219
220 /**
221 * @brief Close the stream and atomically overwrite an existing original file.
222 */
223 template< typename Device, typename Tr, typename Alloc >
224 void tmpfcopystreamTempl<Device,Tr,Alloc>::close()
225 {
226     if (!tmpfstreamTempl<Device,Tr,Alloc>::is_open() || 
227          tmpfstreamTempl<Device,Tr,Alloc>::is_unlinked())
228         return;
229
230     if (get_full_sync())
231         tmpfstreamTempl<Device,Tr,Alloc>::sync();
232
233     // close the underlying filedescriptor
234     tmpfstreamTempl<Device,Tr,Alloc>::close();
235
236     tmpfstreamTempl<Device,Tr,Alloc>::set_file_mode(filemode_on_close);
237
238     if (UserOnClose != I2n::User())
239         tmpfstreamTempl<Device,Tr,Alloc>::set_owner(UserOnClose,GroupOnClose);
240
241     move(get_original_filename(),true);
242
243     if (get_full_sync())
244         dirsync(dirname(get_original_filename()));
245 }
246
247 }
248
249 #endif