Imported Upstream version 1.5.0
[platform/upstream/augeas.git] / tests / test-save.c
1 /*
2  * test-save.c: test various aspects of saving
3  *
4  * Copyright (C) 2009-2015 David Lutterkort
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
19  *
20  * Author: David Lutterkort <lutter@redhat.com>
21  */
22
23 #include <config.h>
24 #include "augeas.h"
25 #include "internal.h"
26 #include "cutest.h"
27
28 #include <stdio.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <unistd.h>
33
34 const char *abs_top_srcdir;
35 const char *abs_top_builddir;
36 char *root = NULL, *src_root = NULL;
37 struct augeas *aug = NULL;
38
39 #define die(msg)                                                    \
40     do {                                                            \
41         fprintf(stderr, "%s:%d: Fatal error: %s\n", __FILE__, __LINE__, msg); \
42         exit(EXIT_FAILURE);                                         \
43     } while(0)
44
45 static void setup(CuTest *tc) {
46     char *lensdir;
47
48     if (asprintf(&root, "%s/build/test-save/%s",
49                  abs_top_builddir, tc->name) < 0) {
50         CuFail(tc, "failed to set root");
51     }
52
53     if (asprintf(&lensdir, "%s/lenses", abs_top_srcdir) < 0)
54         CuFail(tc, "asprintf lensdir failed");
55
56     umask(0022);
57     run(tc, "test -d %s && chmod -R u+w %s || :", root, root);
58     run(tc, "rm -rf %s", root);
59     run(tc, "mkdir -p %s", root);
60     run(tc, "cp -pr %s/* %s", src_root, root);
61     run(tc, "chmod -R u+w %s", root);
62
63     aug = aug_init(root, lensdir, AUG_NO_STDINC);
64     CuAssertPtrNotNull(tc, aug);
65 }
66
67 static void teardown(ATTRIBUTE_UNUSED CuTest *tc) {
68     /* testRemovePermission makes <root>/etc nonwritable. That leads
69        to an error from 'make distcheck' make sure that directory is
70        writable by the user after the test */
71     run(tc, "chmod u+w %s/etc", root);
72
73     aug_close(aug);
74     aug = NULL;
75     free(root);
76     root = NULL;
77 }
78
79 static void testRemoveNoPermission(CuTest *tc) {
80     if (getuid() == 0) {
81         puts("pending (testRemoveNoPermission): can't test permissions under root account");
82         return;
83     }
84
85     int r;
86     const char *errmsg;
87
88     // Prevent deletion of files
89     run(tc, "chmod 0500 %s/etc", root);
90
91     r = aug_rm(aug, "/files/etc/hosts");
92     CuAssertTrue(tc, r > 0);
93
94     r = aug_save(aug);
95     CuAssertIntEquals(tc, -1, r);
96
97     r = aug_get(aug, "/augeas/files/etc/hosts/error", &errmsg);
98     CuAssertIntEquals(tc, 1, r);
99     CuAssertPtrNotNull(tc, errmsg);
100     CuAssertStrEquals(tc, "unlink_orig", errmsg);
101 }
102
103 static void testSaveNewFile(CuTest *tc) {
104     int r;
105
106     r = aug_match(aug, "/augeas/files/etc/yum.repos.d/new.repo/path", NULL);
107     CuAssertIntEquals(tc, 0, r);
108
109     r = aug_set(aug, "/files/etc/yum.repos.d/new.repo/newrepo/baseurl",
110                 "http://foo.com/");
111     CuAssertIntEquals(tc, 0, r);
112
113     r = aug_save(aug);
114     CuAssertIntEquals(tc, 0, r);
115
116     r = aug_match(aug, "/augeas/files/etc/yum.repos.d/new.repo/path", NULL);
117     CuAssertIntEquals(tc, 1, r);
118 }
119
120 static void testNonExistentLens(CuTest *tc) {
121     int r;
122
123     r = aug_rm(aug, "/augeas/load/*");
124     CuAssertTrue(tc, r >= 0);
125
126     r = aug_set(aug, "/augeas/load/Fake/lens", "Fake.lns");
127     CuAssertIntEquals(tc, 0, r);
128     r = aug_set(aug, "/augeas/load/Fake/incl", "/fake");
129     CuAssertIntEquals(tc, 0, r);
130     r = aug_set(aug, "/files/fake/entry", "value");
131     CuAssertIntEquals(tc, 0, r);
132
133     r = aug_save(aug);
134     CuAssertIntEquals(tc, -1, r);
135     r = aug_error(aug);
136     CuAssertIntEquals(tc, AUG_ENOLENS, r);
137 }
138
139 static void testMultipleXfm(CuTest *tc) {
140     int r;
141
142     r = aug_set(aug, "/augeas/load/Yum2/lens", "Yum.lns");
143     CuAssertIntEquals(tc, 0, r);
144     r = aug_set(aug, "/augeas/load/Yum2/incl", "/etc/yum.repos.d/*");
145     CuAssertIntEquals(tc, 0, r);
146
147     r = aug_set(aug, "/files/etc/yum.repos.d/fedora.repo/fedora/enabled", "0");
148     CuAssertIntEquals(tc, 0, r);
149
150     r = aug_save(aug);
151     CuAssertIntEquals(tc, -1, r);
152     r = aug_error(aug);
153     CuAssertIntEquals(tc, AUG_EMXFM, r);
154 }
155
156 static void testMtime(CuTest *tc) {
157     const char *s, *mtime2;
158     char *mtime1;
159     int r;
160
161     r = aug_set(aug, "/files/etc/hosts/1/alias[last() + 1]", "new");
162     CuAssertIntEquals(tc, 0, r);
163
164     r = aug_get(aug, "/augeas/files/etc/hosts/mtime", &s);
165     CuAssertIntEquals(tc, 1, r);
166     mtime1 = strdup(s);
167     CuAssertPtrNotNull(tc, mtime1);
168
169
170     r = aug_save(aug);
171     CuAssertIntEquals(tc, 0, r);
172
173     r = aug_get(aug, "/augeas/files/etc/hosts/mtime", &mtime2);
174     CuAssertIntEquals(tc, 1, r);
175
176     CuAssertStrNotEqual(tc, mtime1, mtime2);
177     CuAssertStrNotEqual(tc, "0", mtime2);
178 }
179
180 /* Check that loading and saving a file given with a relative path
181  * works. Bug #238
182  */
183 static void testRelPath(CuTest *tc) {
184     int r;
185
186     r = aug_rm(aug, "/augeas/load/*");
187     CuAssertPositive(tc, r);
188
189     r = aug_set(aug, "/augeas/load/Hosts/lens", "Hosts.lns");
190     CuAssertRetSuccess(tc, r);
191     r = aug_set(aug, "/augeas/load/Hosts/incl", "etc/hosts");
192     CuAssertRetSuccess(tc, r);
193     r = aug_load(aug);
194     CuAssertRetSuccess(tc, r);
195
196     r = aug_match(aug, "/files/etc/hosts/1/alias[ . = 'new']", NULL);
197     CuAssertIntEquals(tc, 0, r);
198
199     r = aug_set(aug, "/files/etc/hosts/1/alias[last() + 1]", "new");
200     CuAssertRetSuccess(tc, r);
201
202     r = aug_save(aug);
203     CuAssertRetSuccess(tc, r);
204     r = aug_match(aug, "/augeas//error", NULL);
205     CuAssertIntEquals(tc, 0, r);
206
207     /* Force reloading the file */
208     r = aug_rm(aug, "/augeas/files//mtime");
209     CuAssertPositive(tc, r);
210
211     r = aug_load(aug);
212     CuAssertRetSuccess(tc, r);
213
214     r = aug_match(aug, "/files/etc/hosts/1/alias[. = 'new']", NULL);
215     CuAssertIntEquals(tc, 1, r);
216 }
217
218 /* Check that loading and saving a file with // in the incl pattern works.
219  * RHBZ#1031084
220  */
221 static void testDoubleSlashPath(CuTest *tc) {
222     int r;
223
224     r = aug_rm(aug, "/augeas/load/*");
225     CuAssertPositive(tc, r);
226
227     r = aug_set(aug, "/augeas/load/Hosts/lens", "Hosts.lns");
228     CuAssertRetSuccess(tc, r);
229     r = aug_set(aug, "/augeas/load/Hosts/incl", "/etc//hosts");
230     CuAssertRetSuccess(tc, r);
231     r = aug_load(aug);
232     CuAssertRetSuccess(tc, r);
233
234     r = aug_match(aug, "/files/etc/hosts/1/alias[ . = 'new']", NULL);
235     CuAssertIntEquals(tc, 0, r);
236
237     r = aug_set(aug, "/files/etc/hosts/1/alias[last() + 1]", "new");
238     CuAssertRetSuccess(tc, r);
239
240     r = aug_save(aug);
241     CuAssertRetSuccess(tc, r);
242     r = aug_match(aug, "/augeas//error", NULL);
243     CuAssertIntEquals(tc, 0, r);
244
245     /* Force reloading the file */
246     r = aug_rm(aug, "/augeas/files//mtime");
247     CuAssertPositive(tc, r);
248
249     r = aug_load(aug);
250     CuAssertRetSuccess(tc, r);
251
252     r = aug_match(aug, "/files/etc/hosts/1/alias[. = 'new']", NULL);
253     CuAssertIntEquals(tc, 1, r);
254 }
255
256 /* Check the umask is followed when creating files
257  */
258 static void testUmask(CuTest *tc, int tumask, mode_t expected_mode) {
259     int r;
260     struct stat buf;
261     char* fpath = NULL;
262
263     if (asprintf(&fpath, "%s/etc/test", root) < 0) {
264         CuFail(tc, "failed to set root");
265     }
266
267     umask(tumask);
268
269     r = aug_rm(aug, "/augeas/load/*");
270     CuAssertPositive(tc, r);
271
272     r = aug_set(aug, "/augeas/load/Test/lens", "Simplelines.lns");
273     CuAssertRetSuccess(tc, r);
274     r = aug_set(aug, "/augeas/load/Test/incl", "/etc/test");
275     CuAssertRetSuccess(tc, r);
276     r = aug_load(aug);
277     CuAssertRetSuccess(tc, r);
278     r = aug_set(aug, "/files/etc/test/1", "test");
279     CuAssertRetSuccess(tc, r);
280
281     r = aug_save(aug);
282     CuAssertRetSuccess(tc, r);
283     r = aug_match(aug, "/augeas//error", NULL);
284     CuAssertIntEquals(tc, 0, r);
285
286     CuAssertIntEquals(tc, 0, stat(fpath, &buf));
287     CuAssertIntEquals(tc, expected_mode, buf.st_mode & 0777);
288 }
289 static void testUmask077(CuTest *tc) {
290     testUmask(tc, 0077, 0600);
291 }
292 static void testUmask027(CuTest *tc) {
293     testUmask(tc, 0027, 0640);
294 }
295 static void testUmask022(CuTest *tc) {
296     testUmask(tc, 0022, 0644);
297 }
298
299 /* Test that handling of 'strange' characters in path names works as
300  * expected. In particular, that paths with characters that have special
301  * meaning in path expressions are escaped properly.
302  *
303  * This test isn't all that specific to save, but since these tests set up
304  * a copy of tests/root/ that is modifiable, it was convenient to put this
305  * test here.
306  */
307 static void testPathEscaping(CuTest *tc) {
308     /* Path expression with characters escaped */
309     static const char *const weird =
310         "/files/etc/sysconfig/network-scripts/ifcfg-weird\\ \\[\\!\\]\\ \\(used\\ to\\ fail\\)";
311     /* Path without any escaping */
312     static const char *const weird_no_escape =
313         "/files/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail)";
314
315     char *fname = NULL, *s = NULL;
316     const char *v;
317     int r;
318
319     /* Construct the file name in the file system and check the file is there */
320     r = asprintf(&fname, "%s%s", root, weird_no_escape + strlen("/files"));
321     CuAssertPositive(tc, r);
322
323     r = access(fname, R_OK);
324     CuAssertIntEquals(tc, 0, r);
325
326     /* Make sure weird is in the tree */
327     r = aug_match(aug, weird, NULL);
328     CuAssertIntEquals(tc, 1, r);
329
330     /* Make sure we can get to the metadata about weird */
331     r = asprintf(&s, "/augeas%s/path", weird);
332     CuAssertPositive(tc, r);
333
334     r = aug_get(aug, s, &v);
335     CuAssertIntEquals(tc, 1, r);
336     CuAssertStrEquals(tc, weird_no_escape, v);
337
338     /* Delete it from the tree and save it; make sure it gets removed
339        from the file system */
340     r = aug_rm(aug, weird);
341     CuAssertPositive(tc, r);
342
343     r = aug_save(aug);
344     CuAssertRetSuccess(tc, r);
345
346     r = access(fname, R_OK);
347     CuAssertIntEquals(tc, -1, r);
348     CuAssertIntEquals(tc, ENOENT, errno);
349 }
350
351 /* Test that we handle failure to save a file because we lack permission on
352  * the target file is handled gracefully.
353  *
354  * As reported in https://github.com/hercules-team/augeas/issues/178, this
355  * used to lead to a SEGV
356  */
357 static void testSaveNoPermission(CuTest *tc) {
358     if (getuid() == 0) {
359         puts("pending (testSaveNoPermission): can't test permissions under root account");
360         return;
361     }
362
363     int r;
364     char *path = NULL;
365     const char *v;
366
367     r = asprintf(&path, "%s/etc/hosts", root);
368     CuAssertPositive(tc, r);
369
370     r = aug_set(aug, "/files/etc/hosts/1/alias[1]", "othername");
371     CuAssertRetSuccess(tc, r);
372
373     r = chmod(path, 0);
374     CuAssertRetSuccess(tc, r);
375
376     r = aug_save(aug);
377     CuAssertIntEquals(tc, -1, r);
378
379     r = aug_get(aug, "/augeas/files/etc/hosts/error", &v);
380     CuAssertIntEquals(tc, 1, r);
381     CuAssertStrEquals(tc, "replace_from_missing", v);
382 }
383
384 int main(void) {
385     char *output = NULL;
386     CuSuite* suite = CuSuiteNew();
387
388     abs_top_srcdir = getenv("abs_top_srcdir");
389     if (abs_top_srcdir == NULL)
390         die("env var abs_top_srcdir must be set");
391
392     abs_top_builddir = getenv("abs_top_builddir");
393     if (abs_top_builddir == NULL)
394         die("env var abs_top_builddir must be set");
395
396     if (asprintf(&src_root, "%s/tests/root", abs_top_srcdir) < 0) {
397         die("failed to set src_root");
398     }
399
400     CuSuiteSetup(suite, setup, teardown);
401
402     SUITE_ADD_TEST(suite, testSaveNoPermission);
403     SUITE_ADD_TEST(suite, testSaveNewFile);
404     SUITE_ADD_TEST(suite, testRemoveNoPermission);
405     SUITE_ADD_TEST(suite, testNonExistentLens);
406     SUITE_ADD_TEST(suite, testMultipleXfm);
407     SUITE_ADD_TEST(suite, testMtime);
408     SUITE_ADD_TEST(suite, testRelPath);
409     SUITE_ADD_TEST(suite, testDoubleSlashPath);
410     SUITE_ADD_TEST(suite, testUmask077);
411     SUITE_ADD_TEST(suite, testUmask027);
412     SUITE_ADD_TEST(suite, testUmask022);
413     SUITE_ADD_TEST(suite, testPathEscaping);
414
415     CuSuiteRun(suite);
416     CuSuiteSummary(suite, &output);
417     CuSuiteDetails(suite, &output);
418     printf("%s\n", output);
419     free(output);
420     return suite->failCount;
421 }
422
423 /*
424  * Local variables:
425  *  indent-tabs-mode: nil
426  *  c-indent-level: 4
427  *  c-basic-offset: 4
428  *  tab-width: 4
429  * End:
430  */