Implement safe_fclose() so we can respect the "Metadata handling"
[claws.git] / src / common / prefs.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #include "defs.h"
26
27 #include <glib.h>
28
29 #include "prefs.h"
30 #include "utils.h"
31 #include "safe_fclose.h"
32
33 static gboolean prefs_is_readonly       (const gchar    *path);
34
35 /*!
36  *\brief        Open preferences file for reading
37  *
38  *\param        path Filename with path of preferences file to read
39  *
40  *\return       PrefFile * preferences file struct
41  */
42 PrefFile *prefs_read_open(const gchar *path)
43 {
44         PrefFile *pfile;
45         FILE *fp;
46
47         cm_return_val_if_fail(path != NULL, NULL);
48
49         if ((fp = g_fopen(path, "rb")) == NULL) {
50                 FILE_OP_ERROR(path, "fopen");
51                 return NULL;
52         }
53
54         pfile = g_new(PrefFile, 1);
55         pfile->fp = fp;
56         pfile->orig_fp = NULL;
57         pfile->path = g_strdup(path);
58         pfile->writing = FALSE;
59
60         return pfile;
61 }
62
63 /*!
64  *\brief        Open preferences file for writing
65  *              Prefs are written to a temp file: Call prefs_file_close()
66  *              to rename this to the final filename
67  *
68  *\param        path Filename with path of preferences file to write
69  *
70  *\return       PrefFile * preferences file struct
71  */
72 PrefFile *prefs_write_open(const gchar *path)
73 {
74         PrefFile *pfile;
75         gchar *tmppath;
76         FILE *fp;
77
78         cm_return_val_if_fail(path != NULL, NULL);
79
80         if (prefs_is_readonly(path)) {
81                 g_warning("no write permission on '%s'", path);
82                 return NULL;
83         }
84
85         tmppath = g_strconcat(path, ".tmp", NULL);
86         if ((fp = g_fopen(tmppath, "wb")) == NULL) {
87                 FILE_OP_ERROR(tmppath, "fopen");
88                 g_free(tmppath);
89                 return NULL;
90         }
91
92         if (change_file_mode_rw(fp, tmppath) < 0)
93                 FILE_OP_ERROR(tmppath, "chmod");
94
95         g_free(tmppath);
96
97         pfile = g_new(PrefFile, 1);
98         pfile->fp = fp;
99         pfile->orig_fp = NULL;
100         pfile->path = g_strdup(path);
101         pfile->writing = TRUE;
102
103         return pfile;
104 }
105
106 gboolean prefs_common_get_flush_metadata (void);
107
108 /*!
109  *\brief        Close and free preferences file
110  *              Creates final file from temp, creates backup
111  *
112  *\param        pfile Preferences file struct
113  *
114  *\return       0 on success, -1 on failure
115  */
116 gint prefs_file_close(PrefFile *pfile)
117 {
118         FILE *fp, *orig_fp;
119         gchar *path;
120         gchar *tmppath;
121         gchar *bakpath = NULL;
122         gchar buf[BUFFSIZE];
123
124         cm_return_val_if_fail(pfile != NULL, -1);
125
126         fp = pfile->fp;
127         orig_fp = pfile->orig_fp;
128         path = pfile->path;
129
130         if (!pfile->writing) {
131                 fclose(fp);
132                 g_free(pfile);
133                 g_free(path);
134                 return 0;
135         }
136
137         if (orig_fp) {
138                 while (fgets(buf, sizeof(buf), orig_fp) != NULL) {
139                         /* next block */
140                         if (buf[0] == '[') {
141                                 if (fputs(buf, fp)  == EOF) {
142                                         g_warning("failed to write configuration to file");
143                                         prefs_file_close_revert(pfile);
144                                 
145                                         return -1;
146                                 }
147                                 break;
148                         }
149                 }
150                 
151                 while (fgets(buf, sizeof(buf), orig_fp) != NULL)
152                         if (fputs(buf, fp) == EOF) {
153                                 g_warning("failed to write configuration to file");
154                                 prefs_file_close_revert(pfile);                 
155                                 
156                                 return -1;
157                         }
158                 fclose(orig_fp);
159         }
160
161         tmppath = g_strconcat(path, ".tmp", NULL);
162
163         if (safe_fclose(fp) == EOF) {
164                 FILE_OP_ERROR(tmppath, "fclose");
165                 claws_unlink(tmppath);
166                 g_free(path);
167                 g_free(tmppath);
168                 return -1;
169         }
170
171         if (is_file_exist(path)) {
172                 bakpath = g_strconcat(path, ".bak", NULL);
173 #ifdef G_OS_WIN32
174                 claws_unlink(bakpath);
175 #endif
176                 if (g_rename(path, bakpath) < 0) {
177                         FILE_OP_ERROR(path, "rename");
178                         claws_unlink(tmppath);
179                         g_free(path);
180                         g_free(tmppath);
181                         g_free(bakpath);
182                         return -1;
183                 }
184         }
185
186 #ifdef G_OS_WIN32
187         claws_unlink(path);
188 #endif
189         if (g_rename(tmppath, path) < 0) {
190                 FILE_OP_ERROR(tmppath, "rename");
191                 claws_unlink(tmppath);
192                 g_free(path);
193                 g_free(tmppath);
194                 g_free(bakpath);
195                 return -1;
196         }
197
198         g_free(pfile);
199         g_free(path);
200         g_free(tmppath);
201         g_free(bakpath);
202         return 0;
203 }
204
205 /*!
206  *\brief        Close and free preferences file, delete temp file
207  *
208  *\param        pfile Preferences file struct
209  */
210 gint prefs_file_close_revert(PrefFile *pfile)
211 {
212         gchar *tmppath = NULL;
213
214         cm_return_val_if_fail(pfile != NULL, -1);
215
216         if (pfile->orig_fp)
217                 fclose(pfile->orig_fp);
218         if (pfile->writing)
219                 tmppath = g_strconcat(pfile->path, ".tmp", NULL);
220         fclose(pfile->fp);
221         if (pfile->writing) {
222                 if (claws_unlink(tmppath) < 0) FILE_OP_ERROR(tmppath, "unlink");
223                 g_free(tmppath);
224         }
225         g_free(pfile->path);
226         g_free(pfile);
227
228         return 0;
229 }
230
231 /*!
232  *\brief        Check if "path" is a file and readonly
233  */
234 static gboolean prefs_is_readonly(const gchar * path)
235 {
236         if (path == NULL)
237                 return TRUE;
238
239         return (access(path, W_OK) != 0 && access(path, F_OK) == 0);
240 }
241
242 /*!
243  *\brief        Check if "rcfile" is in rcdir, a file and readonly
244  */
245 gboolean prefs_rc_is_readonly(const gchar * rcfile)
246 {
247         gboolean result;
248         gchar * rcpath;
249
250         if (rcfile == NULL)
251                 return TRUE;
252
253         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, rcfile, NULL);
254         result = prefs_is_readonly(rcpath);
255         g_free(rcpath);
256
257         return result;
258 }
259
260 /*!
261  *\brief        Selects current section in preferences file
262  *              Creates section if file is written
263  *
264  *\param        pfile Preferences file struct
265  *
266  *\return       0 on success, -1 on failure
267  */
268 gint prefs_set_block_label(PrefFile *pfile, const gchar *label)
269 {
270         gchar *block_label;
271         gchar buf[BUFFSIZE];
272
273         block_label = g_strdup_printf("[%s]", label);
274         if (!pfile->writing) {
275                 while (fgets(buf, sizeof(buf), pfile->fp) != NULL) {
276                         gint val;
277                         
278                         val = strncmp(buf, block_label, strlen(block_label));
279                         if (val == 0) {
280                                 debug_print("Found %s\n", block_label);
281                                 break;
282                         }
283                 }
284         } else {
285                 if ((pfile->orig_fp = g_fopen(pfile->path, "rb")) != NULL) {
286                         gboolean block_matched = FALSE;
287
288                         while (fgets(buf, sizeof(buf), pfile->orig_fp) != NULL) {
289                                 gint val;
290
291                                 val = strncmp(buf, block_label, strlen(block_label));
292                                 if (val == 0) {
293                                         debug_print("Found %s\n", block_label);
294                                         block_matched = TRUE;
295                                         break;
296                                 } else {
297                                         if (fputs(buf, pfile->fp) == EOF) {
298                                                 g_warning("failed to write configuration to file");
299                                                 prefs_file_close_revert(pfile);
300                                                 g_free(block_label);
301                                                 
302                                                 return -1;
303                                         }
304                                 }
305                         }
306
307                         if (!block_matched) {
308                                 fclose(pfile->orig_fp);
309                                 pfile->orig_fp = NULL;
310                         }
311                 }
312
313                 if (fputs(block_label, pfile->fp) == EOF ||
314                         fputc('\n', pfile->fp) == EOF) {
315                         g_warning("failed to write configuration to file");
316                         prefs_file_close_revert(pfile);
317                         g_free(block_label);
318
319                         return -1;
320                 }
321         }
322
323         g_free(block_label);
324
325         return 0;
326 }