Bump to 1.14.1
[platform/upstream/augeas.git] / tests / test-save.c
1 /*
2  * test-save.c: test various aspects of saving
3  *
4  * Copyright (C) 2009-2016 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     free(lensdir);
65     CuAssertPtrNotNull(tc, aug);
66 }
67
68 static void teardown(ATTRIBUTE_UNUSED CuTest *tc) {
69     /* testRemovePermission makes <root>/etc nonwritable. That leads
70        to an error from 'make distcheck' make sure that directory is
71        writable by the user after the test */
72     run(tc, "chmod u+w %s/etc", root);
73
74     aug_close(aug);
75     aug = NULL;
76     free(root);
77     root = NULL;
78 }
79
80 static void testRemoveNoPermission(CuTest *tc) {
81     if (getuid() == 0) {
82         puts("pending (testRemoveNoPermission): can't test permissions under root account");
83         return;
84     }
85
86     int r;
87     const char *errmsg;
88
89     // Prevent deletion of files
90     run(tc, "chmod 0500 %s/etc", root);
91
92     r = aug_rm(aug, "/files/etc/hosts");
93     CuAssertTrue(tc, r > 0);
94
95     r = aug_save(aug);
96     CuAssertIntEquals(tc, -1, r);
97
98     r = aug_get(aug, "/augeas/files/etc/hosts/error", &errmsg);
99     CuAssertIntEquals(tc, 1, r);
100     CuAssertPtrNotNull(tc, errmsg);
101     CuAssertStrEquals(tc, "unlink_orig", errmsg);
102 }
103
104 static void testSaveNewFile(CuTest *tc) {
105     int r;
106
107     r = aug_match(aug, "/augeas/files/etc/yum.repos.d/new.repo/path", NULL);
108     CuAssertIntEquals(tc, 0, r);
109
110     r = aug_set(aug, "/files/etc/yum.repos.d/new.repo/newrepo/baseurl",
111                 "http://foo.com/");
112     CuAssertIntEquals(tc, 0, r);
113
114     r = aug_save(aug);
115     CuAssertIntEquals(tc, 0, r);
116
117     r = aug_match(aug, "/augeas/files/etc/yum.repos.d/new.repo/path", NULL);
118     CuAssertIntEquals(tc, 1, r);
119 }
120
121 static void testNonExistentLens(CuTest *tc) {
122     int r;
123
124     r = aug_rm(aug, "/augeas/load/*");
125     CuAssertTrue(tc, r >= 0);
126
127     r = aug_set(aug, "/augeas/load/Fake/lens", "Fake.lns");
128     CuAssertIntEquals(tc, 0, r);
129     r = aug_set(aug, "/augeas/load/Fake/incl", "/fake");
130     CuAssertIntEquals(tc, 0, r);
131     r = aug_set(aug, "/files/fake/entry", "value");
132     CuAssertIntEquals(tc, 0, r);
133
134     r = aug_save(aug);
135     CuAssertIntEquals(tc, -1, r);
136     r = aug_error(aug);
137     CuAssertIntEquals(tc, AUG_ENOLENS, r);
138 }
139
140 static void testMultipleXfm(CuTest *tc) {
141     int r;
142
143     r = aug_set(aug, "/augeas/load/Yum2/lens", "Yum.lns");
144     CuAssertIntEquals(tc, 0, r);
145     r = aug_set(aug, "/augeas/load/Yum2/incl", "/etc/yum.repos.d/*");
146     CuAssertIntEquals(tc, 0, r);
147
148     r = aug_set(aug, "/files/etc/yum.repos.d/fedora.repo/fedora/enabled", "0");
149     CuAssertIntEquals(tc, 0, r);
150     /* What we have set up is fine: two ways to save the same file with the
151        same lens */
152     r = aug_save(aug);
153     CuAssertIntEquals(tc, 0, r);
154
155     /* Now we make it bad: a different lens for the same file */
156     r = aug_set(aug, "/augeas/load/Yum2/lens", "@Subversion");
157     CuAssertIntEquals(tc, 0, r);
158
159     r = aug_set(aug, "/files/etc/yum.repos.d/fedora.repo/fedora/enabled", "1");
160     CuAssertIntEquals(tc, 0, r);
161
162     r = aug_save(aug);
163     CuAssertIntEquals(tc, -1, r);
164
165     r = aug_error(aug);
166     CuAssertIntEquals(tc, AUG_EMXFM, r);
167 }
168
169 static void testMtime(CuTest *tc) {
170     const char *s, *mtime2;
171     char *mtime1;
172     int r;
173
174     r = aug_set(aug, "/files/etc/hosts/1/alias[last() + 1]", "new");
175     CuAssertIntEquals(tc, 0, r);
176
177     r = aug_get(aug, "/augeas/files/etc/hosts/mtime", &s);
178     CuAssertIntEquals(tc, 1, r);
179     mtime1 = strdup(s);
180     CuAssertPtrNotNull(tc, mtime1);
181
182
183     r = aug_save(aug);
184     CuAssertIntEquals(tc, 0, r);
185
186     r = aug_get(aug, "/augeas/files/etc/hosts/mtime", &mtime2);
187     CuAssertIntEquals(tc, 1, r);
188
189     CuAssertStrNotEqual(tc, mtime1, mtime2);
190     CuAssertStrNotEqual(tc, "0", mtime2);
191     free(mtime1);
192 }
193
194 /* Check that loading and saving a file given with a relative path
195  * works. Bug #238
196  */
197 static void testRelPath(CuTest *tc) {
198     int r;
199
200     r = aug_rm(aug, "/augeas/load/*");
201     CuAssertPositive(tc, r);
202
203     r = aug_set(aug, "/augeas/load/Hosts/lens", "Hosts.lns");
204     CuAssertRetSuccess(tc, r);
205     r = aug_set(aug, "/augeas/load/Hosts/incl", "etc/hosts");
206     CuAssertRetSuccess(tc, r);
207     r = aug_load(aug);
208     CuAssertRetSuccess(tc, r);
209
210     r = aug_match(aug, "/files/etc/hosts/1/alias[ . = 'new']", NULL);
211     CuAssertIntEquals(tc, 0, r);
212
213     r = aug_set(aug, "/files/etc/hosts/1/alias[last() + 1]", "new");
214     CuAssertRetSuccess(tc, r);
215
216     r = aug_save(aug);
217     CuAssertRetSuccess(tc, r);
218     r = aug_match(aug, "/augeas//error", NULL);
219     CuAssertIntEquals(tc, 0, r);
220
221     /* Force reloading the file */
222     r = aug_rm(aug, "/augeas/files//mtime");
223     CuAssertPositive(tc, r);
224
225     r = aug_load(aug);
226     CuAssertRetSuccess(tc, r);
227
228     r = aug_match(aug, "/files/etc/hosts/1/alias[. = 'new']", NULL);
229     CuAssertIntEquals(tc, 1, r);
230 }
231
232 /* Check that loading and saving a file with // in the incl pattern works.
233  * RHBZ#1031084
234  */
235 static void testDoubleSlashPath(CuTest *tc) {
236     int r;
237
238     r = aug_rm(aug, "/augeas/load/*");
239     CuAssertPositive(tc, r);
240
241     r = aug_set(aug, "/augeas/load/Hosts/lens", "Hosts.lns");
242     CuAssertRetSuccess(tc, r);
243     r = aug_set(aug, "/augeas/load/Hosts/incl", "/etc//hosts");
244     CuAssertRetSuccess(tc, r);
245     r = aug_load(aug);
246     CuAssertRetSuccess(tc, r);
247
248     r = aug_match(aug, "/files/etc/hosts/1/alias[ . = 'new']", NULL);
249     CuAssertIntEquals(tc, 0, r);
250
251     r = aug_set(aug, "/files/etc/hosts/1/alias[last() + 1]", "new");
252     CuAssertRetSuccess(tc, r);
253
254     r = aug_save(aug);
255     CuAssertRetSuccess(tc, r);
256     r = aug_match(aug, "/augeas//error", NULL);
257     CuAssertIntEquals(tc, 0, r);
258
259     /* Force reloading the file */
260     r = aug_rm(aug, "/augeas/files//mtime");
261     CuAssertPositive(tc, r);
262
263     r = aug_load(aug);
264     CuAssertRetSuccess(tc, r);
265
266     r = aug_match(aug, "/files/etc/hosts/1/alias[. = 'new']", NULL);
267     CuAssertIntEquals(tc, 1, r);
268 }
269
270 /* Check the umask is followed when creating files
271  */
272 static void testUmask(CuTest *tc, int tumask, mode_t expected_mode) {
273     int r;
274     struct stat buf;
275     char* fpath = NULL;
276
277     if (asprintf(&fpath, "%s/etc/test", root) < 0) {
278         CuFail(tc, "failed to set root");
279     }
280
281     umask(tumask);
282
283     r = aug_rm(aug, "/augeas/load/*");
284     CuAssertPositive(tc, r);
285
286     r = aug_set(aug, "/augeas/load/Test/lens", "Simplelines.lns");
287     CuAssertRetSuccess(tc, r);
288     r = aug_set(aug, "/augeas/load/Test/incl", "/etc/test");
289     CuAssertRetSuccess(tc, r);
290     r = aug_load(aug);
291     CuAssertRetSuccess(tc, r);
292     r = aug_set(aug, "/files/etc/test/1", "test");
293     CuAssertRetSuccess(tc, r);
294
295     r = aug_save(aug);
296     CuAssertRetSuccess(tc, r);
297     r = aug_match(aug, "/augeas//error", NULL);
298     CuAssertIntEquals(tc, 0, r);
299
300     CuAssertIntEquals(tc, 0, stat(fpath, &buf));
301     CuAssertIntEquals(tc, expected_mode, buf.st_mode & 0777);
302     free(fpath);
303 }
304 static void testUmask077(CuTest *tc) {
305     testUmask(tc, 0077, 0600);
306 }
307 static void testUmask027(CuTest *tc) {
308     testUmask(tc, 0027, 0640);
309 }
310 static void testUmask022(CuTest *tc) {
311     testUmask(tc, 0022, 0644);
312 }
313
314 /* Test that handling of 'strange' characters in path names works as
315  * expected. In particular, that paths with characters that have special
316  * meaning in path expressions are escaped properly.
317  *
318  * This test isn't all that specific to save, but since these tests set up
319  * a copy of tests/root/ that is modifiable, it was convenient to put this
320  * test here.
321  */
322 static void testPathEscaping(CuTest *tc) {
323     /* Path expression with characters escaped */
324     static const char *const weird =
325         "/files/etc/sysconfig/network-scripts/ifcfg-weird\\ \\[\\!\\]\\ \\(used\\ to\\ fail\\)";
326     /* Path without any escaping */
327     static const char *const weird_no_escape =
328         "/files/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail)";
329
330     char *fname = NULL, *s = NULL;
331     const char *v;
332     int r;
333
334     /* Construct the file name in the file system and check the file is there */
335     r = asprintf(&fname, "%s%s", root, weird_no_escape + strlen("/files"));
336     CuAssertPositive(tc, r);
337
338     r = access(fname, R_OK);
339     CuAssertIntEquals(tc, 0, r);
340
341     /* Make sure weird is in the tree */
342     r = aug_match(aug, weird, NULL);
343     CuAssertIntEquals(tc, 1, r);
344
345     /* Make sure we can get to the metadata about weird */
346     r = asprintf(&s, "/augeas%s/path", weird);
347     CuAssertPositive(tc, r);
348
349     r = aug_get(aug, s, &v);
350     CuAssertIntEquals(tc, 1, r);
351     CuAssertStrEquals(tc, weird_no_escape, v);
352
353     /* Delete it from the tree and save it; make sure it gets removed
354        from the file system */
355     r = aug_rm(aug, weird);
356     CuAssertPositive(tc, r);
357
358     r = aug_save(aug);
359     CuAssertRetSuccess(tc, r);
360
361     r = access(fname, R_OK);
362     CuAssertIntEquals(tc, -1, r);
363     CuAssertIntEquals(tc, ENOENT, errno);
364
365     free(s);
366     free(fname);
367 }
368
369 /* Test that we handle failure to save a file because we lack permission on
370  * the target file is handled gracefully.
371  *
372  * As reported in https://github.com/hercules-team/augeas/issues/178, this
373  * used to lead to a SEGV
374  */
375 static void testSaveNoPermission(CuTest *tc) {
376     if (getuid() == 0) {
377         puts("pending (testSaveNoPermission): can't test permissions under root account");
378         return;
379     }
380
381     int r;
382     char *path = NULL;
383     const char *v;
384
385     r = asprintf(&path, "%s/etc/hosts", root);
386     CuAssertPositive(tc, r);
387
388     r = aug_set(aug, "/files/etc/hosts/1/alias[1]", "othername");
389     CuAssertRetSuccess(tc, r);
390
391     r = chmod(path, 0);
392     CuAssertRetSuccess(tc, r);
393
394     r = aug_save(aug);
395     CuAssertIntEquals(tc, -1, r);
396
397     r = aug_get(aug, "/augeas/files/etc/hosts/error", &v);
398     CuAssertIntEquals(tc, 1, r);
399     CuAssertStrEquals(tc, "replace_from_missing", v);
400     free(path);
401 }
402
403 int main(void) {
404     char *output = NULL;
405     CuSuite* suite = CuSuiteNew();
406
407     abs_top_srcdir = getenv("abs_top_srcdir");
408     if (abs_top_srcdir == NULL)
409         die("env var abs_top_srcdir must be set");
410
411     abs_top_builddir = getenv("abs_top_builddir");
412     if (abs_top_builddir == NULL)
413         die("env var abs_top_builddir must be set");
414
415     if (asprintf(&src_root, "%s/tests/root", abs_top_srcdir) < 0) {
416         die("failed to set src_root");
417     }
418
419     CuSuiteSetup(suite, setup, teardown);
420
421     SUITE_ADD_TEST(suite, testSaveNoPermission);
422     SUITE_ADD_TEST(suite, testSaveNewFile);
423     SUITE_ADD_TEST(suite, testRemoveNoPermission);
424     SUITE_ADD_TEST(suite, testNonExistentLens);
425     SUITE_ADD_TEST(suite, testMultipleXfm);
426     SUITE_ADD_TEST(suite, testMtime);
427     SUITE_ADD_TEST(suite, testRelPath);
428     SUITE_ADD_TEST(suite, testDoubleSlashPath);
429     SUITE_ADD_TEST(suite, testUmask077);
430     SUITE_ADD_TEST(suite, testUmask027);
431     SUITE_ADD_TEST(suite, testUmask022);
432     SUITE_ADD_TEST(suite, testPathEscaping);
433
434     CuSuiteRun(suite);
435     CuSuiteSummary(suite, &output);
436     CuSuiteDetails(suite, &output);
437     printf("%s\n", output);
438     free(output);
439     int result = suite->failCount;
440     CuSuiteFree(suite);
441     return result;
442 }
443
444 /*
445  * Local variables:
446  *  indent-tabs-mode: nil
447  *  c-indent-level: 4
448  *  c-basic-offset: 4
449  *  tab-width: 4
450  * End:
451  */