1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * Copyright (C) 2013-2015 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU Lesser General Public License Version 2.1
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or(at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
20  */
21 
22 /**
23  * SECTION:dnf-utils
24  * @short_description: Helper functions for libdnf
25  * @include: libdnf.h
26  * @stability: Unstable
27  *
28  * These functions are used internally in libdnf for various things.
29  */
30 
31 
32 #include <errno.h>
33 #include <stdlib.h>
34 #include <glib/gstdio.h>
35 
36 #include "catch-error.hpp"
37 #include "dnf-types.h"
38 #include "dnf-utils.h"
39 
40 #include "utils/bgettext/bgettext-lib.h"
41 
42 /**
43  * dnf_error_quark:
44  *
45  * Returns a #GQuark for the error domain used in libdnf
46  *
47  * Returns: an error quark
48  **/
49 GQuark
dnf_error_quark(void)50 dnf_error_quark(void)
51 {
52     static GQuark quark = 0;
53     if (!quark)
54         quark = g_quark_from_static_string("DnfError");
55     return quark;
56 }
57 
58 /**
59  * dnf_realpath:
60  * @path: A relative path, e.g. "../../data/test"
61  *
62  * Converts relative paths to absolute ones.
63  *
64  * Returns: a new path, or %NULL
65  *
66  * Since: 0.1.7
67  **/
68 gchar *
dnf_realpath(const gchar * path)69 dnf_realpath(const gchar *path)
70 {
71     gchar *real = NULL;
72     char *temp;
73 
74     /* don't trust realpath one little bit */
75     if (path == NULL)
76         return NULL;
77 
78     /* glibc allocates us a buffer to try and fix some brain damage */
79     temp = realpath(path, NULL);
80     if (temp == NULL)
81         return NULL;
82     real = g_strdup(temp);
83     free(temp);
84     return real;
85 }
86 
87 /**
88  * dnf_remove_recursive:
89  * @directory: A directory path
90  * @error: A #GError, or %NULL
91  *
92  * Removes a directory and its contents. Use with care.
93  *
94  * Returns: %FALSE if an error was set
95  *
96  * Since: 0.1.7
97  **/
98 gboolean
dnf_remove_recursive(const gchar * directory,GError ** error)99 dnf_remove_recursive(const gchar *directory, GError **error) try
100 {
101     const gchar *filename;
102     g_autoptr(GDir) dir = NULL;
103     g_autoptr(GError) error_local = NULL;
104 
105     /* try to open */
106     dir = g_dir_open(directory, 0, &error_local);
107     if (dir == NULL) {
108         g_set_error(error,
109                     DNF_ERROR,
110                     DNF_ERROR_INTERNAL_ERROR,
111                     _("cannot open directory %1$s: %2$s"),
112                     directory, error_local->message);
113         return FALSE;
114     }
115 
116     /* find each */
117     while ((filename = g_dir_read_name(dir))) {
118         g_autofree gchar *src = NULL;
119         src = g_build_filename(directory, filename, NULL);
120         if (g_file_test(src, G_FILE_TEST_IS_DIR)) {
121             if (!dnf_remove_recursive(src, error))
122                 return FALSE;
123         } else {
124             g_debug("deleting file %s", src);
125             if (!dnf_ensure_file_unlinked(src, error))
126                 return FALSE;
127         }
128     }
129 
130     /* remove directory */
131     g_debug("deleting directory %s", directory);
132     if (g_remove(directory) != 0) {
133         g_set_error(error,
134                     DNF_ERROR,
135                     DNF_ERROR_INTERNAL_ERROR,
136                     _("failed to remove %s"), directory);
137         return FALSE;
138     }
139     return TRUE;
140 } CATCH_TO_GERROR(FALSE)
141 
142 
143 /**
144  * dnf_ensure_file_unlinked:
145  * @src_path: the path to the file
146  * @error: A #GError, or %NULL
147  *
148  * Remove a file based on the file path,
149  * refactored from dnf_remove_recursive() function
150  *
151  * Returns: %FALSE if an error was set
152  *
153  * Since 0.9.4
154  **/
155 gboolean
156 dnf_ensure_file_unlinked(const gchar *src_path, GError **error) try
157 {
158     if ((unlink(src_path) != 0) && errno != ENOENT) {
159         g_set_error(error,
160                     DNF_ERROR,
161                     DNF_ERROR_INTERNAL_ERROR,
162                     "failed to unlink %s", src_path);
163         return FALSE;
164     }
165 
166     return TRUE;
CATCH_TO_GERROR(FALSE)167 } CATCH_TO_GERROR(FALSE)
168 
169 /**
170  * dnf_delete_files_matching:
171  * @directory_path: the top level directory path to look at
172  * @patterns: the patterns that we are expecting from file/directory's name
173  * @error: a #GError instance, to track error
174  *
175  * Remove recursively all the files/directories that have names
176  * which match the patterns. Use with care
177  *
178  * There are several assumptions that are made in this function:
179  *
180  * 1: We assume the top level path( the path passed in initially)
181  * does not satisfy the criteria of matching the pattern
182  *
183  * 2: We assume the top level path itself is a directory initially
184  *
185  * Returns: %FALSE if failed to delete a file/directory
186  *
187  * Since: 0.9.4
188  **/
189 gboolean
190 dnf_delete_files_matching(const gchar* directory_path,
191                           const char* const* patterns,
192                           GError **error) try
193 {
194     const gchar *filename;
195     g_autoptr(GDir) dir = NULL;
196 
197     /* try to open the directory*/
198     dir = g_dir_open(directory_path, 0, error);
199     if (dir == NULL) {
200         g_prefix_error(error, "Cannot open directory %s: ", directory_path);
201         return FALSE;
202     }
203     /* In the directory, we read each file and check if their name matches one of the patterns */
204     while ((filename = g_dir_read_name(dir))) {
205         g_autofree gchar *src = g_build_filename(directory_path, filename, NULL);
206         if (g_file_test(src, G_FILE_TEST_IS_DIR)) {
207             gboolean matching = FALSE;
208             for (char **iter = (char **) patterns; iter && *iter; iter++) {
209                 const char* pattern = *iter;
210                 if (g_str_has_suffix(filename, pattern)) {
211                     if (!dnf_remove_recursive(src, error))
212                         return FALSE;
213                     matching = TRUE;
214                     break;
215                 }
216             }
217             if (!matching) {
218                 /* If the directory does not match pattern, keep looking into it */
219                 if (!dnf_delete_files_matching(src, patterns, error))
220                    return FALSE;
221             }
222         }
223         else {
224             /* This is for files in the directory, if matches, we directly delete it */
225             for (char **iter = (char **)patterns; iter && *iter; iter++) {
226                 const char* pattern = *iter;
227                 if (g_str_has_suffix(filename, pattern)) {
228                     if (!dnf_ensure_file_unlinked(src, error))
229                         return FALSE;
230                     break;
231                 }
232             }
233         }
234     }
235 
236     return TRUE;
237 } CATCH_TO_GERROR(FALSE)
238 
239 /**
240  * dnf_get_file_contents_allow_noent:
241  * @path: File to open
242  * @out_contents: Output variable containing contents
243  * @out_length: Output variable containing length
244  * @error: A #GError, or %NULL
245  *
246  * Reads a file's contents. If the file does not exist, returns TRUE.
247  *
248  * Returns: %FALSE if an error other than G_FILE_ERROR_NOENT was set
249  *
250  * Since: 0.1.7
251  **/
252 gboolean
253 dnf_get_file_contents_allow_noent(const gchar            *path,
254                                   gchar                  **out_contents,
255                                   gsize                  *out_length,
256                                   GError                 **error) try
257 {
258     gsize length;
259     g_autofree gchar *contents = NULL;
260     g_autoptr(GError) local_error = NULL;
261 
262     if (!g_file_get_contents(path, &contents, &length, &local_error)) {
263         if (g_error_matches(local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
264             return TRUE;
265 
266         g_propagate_error(error, local_error);
267         return FALSE;
268     }
269 
270     if (out_contents != NULL)
271         *out_contents = static_cast<gchar *>(g_steal_pointer (&contents));
272     if (out_length != NULL)
273         *out_length = length;
274 
275     return TRUE;
276 } CATCH_TO_GERROR(FALSE)
277