1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2011-2018 Karel Zak <kzak@redhat.com>
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 */
12
13 /**
14 * SECTION: tabdiff
15 * @title: Compare changes in mount tables
16 * @short_description: compare changes in the list of the mounted filesystems
17 */
18 #include "mountP.h"
19
20 struct tabdiff_entry {
21 int oper; /* MNT_TABDIFF_* flags; */
22
23 struct libmnt_fs *old_fs; /* pointer to the old FS */
24 struct libmnt_fs *new_fs; /* pointer to the new FS */
25
26 struct list_head changes;
27 };
28
29 struct libmnt_tabdiff {
30 int nchanges; /* number of changes */
31
32 struct list_head changes; /* list with modified entries */
33 struct list_head unused; /* list with unused entries */
34 };
35
36 /**
37 * mnt_new_tabdiff:
38 *
39 * Allocates a new table diff struct.
40 *
41 * Returns: new diff handler or NULL.
42 */
43 struct libmnt_tabdiff *mnt_new_tabdiff(void)
44 {
45 struct libmnt_tabdiff *df = calloc(1, sizeof(*df));
46
47 if (!df)
48 return NULL;
49
50 DBG(DIFF, ul_debugobj(df, "alloc"));
51
52 INIT_LIST_HEAD(&df->changes);
53 INIT_LIST_HEAD(&df->unused);
54 return df;
55 }
56
57 static void free_tabdiff_entry(struct tabdiff_entry *de)
58 {
59 if (!de)
60 return;
61 list_del(&de->changes);
62 mnt_unref_fs(de->new_fs);
63 mnt_unref_fs(de->old_fs);
64 free(de);
65 }
66
67 /**
68 * mnt_free_tabdiff:
69 * @df: tab diff
70 *
71 * Deallocates tab diff struct and all entries.
72 */
73 void mnt_free_tabdiff(struct libmnt_tabdiff *df)
74 {
75 if (!df)
76 return;
77
78 DBG(DIFF, ul_debugobj(df, "free"));
79
80 while (!list_empty(&df->changes)) {
81 struct tabdiff_entry *de = list_entry(df->changes.next,
82 struct tabdiff_entry, changes);
83 free_tabdiff_entry(de);
84 }
85
86 free(df);
87 }
88
89 /**
90 * mnt_tabdiff_next_change:
91 * @df: tabdiff pointer
92 * @itr: iterator
93 * @old_fs: returns the old entry or NULL if new entry added
94 * @new_fs: returns the new entry or NULL if old entry removed
95 * @oper: MNT_TABDIFF_{MOVE,UMOUNT,REMOUNT,MOUNT} flags
96 *
97 * The options @old_fs, @new_fs and @oper are optional.
98 *
99 * Returns: 0 on success, negative number in case of error or 1 at the end of list.
100 */
101 int mnt_tabdiff_next_change(struct libmnt_tabdiff *df, struct libmnt_iter *itr,
102 struct libmnt_fs **old_fs, struct libmnt_fs **new_fs, int *oper)
103 {
104 int rc = 1;
105 struct tabdiff_entry *de = NULL;
106
107 if (!df || !itr)
108 return -EINVAL;
109
110 if (!itr->head)
111 MNT_ITER_INIT(itr, &df->changes);
112 if (itr->p != itr->head) {
113 de = MNT_ITER_GET_ENTRY(itr, struct tabdiff_entry, changes);
114 MNT_ITER_ITERATE(itr);
115 rc = 0;
116 }
117
118 if (old_fs)
119 *old_fs = de ? de->old_fs : NULL;
120 if (new_fs)
121 *new_fs = de ? de->new_fs : NULL;
122 if (oper)
123 *oper = de ? de->oper : 0;
124
125 return rc;
126 }
127
128 static int tabdiff_reset(struct libmnt_tabdiff *df)
129 {
130 assert(df);
131
132 DBG(DIFF, ul_debugobj(df, "resetting"));
133
134 /* zeroize all entries and move them to the list of unused
135 */
136 while (!list_empty(&df->changes)) {
137 struct tabdiff_entry *de = list_entry(df->changes.next,
138 struct tabdiff_entry, changes);
139
140 list_del_init(&de->changes);
141 list_add_tail(&de->changes, &df->unused);
142
143 mnt_unref_fs(de->new_fs);
144 mnt_unref_fs(de->old_fs);
145
146 de->new_fs = de->old_fs = NULL;
147 de->oper = 0;
148 }
149
150 df->nchanges = 0;
151 return 0;
152 }
153
154 static int tabdiff_add_entry(struct libmnt_tabdiff *df, struct libmnt_fs *old,
155 struct libmnt_fs *new, int oper)
156 {
157 struct tabdiff_entry *de;
158
159 assert(df);
160
161 DBG(DIFF, ul_debugobj(df, "add change on %s",
162 mnt_fs_get_target(new ? new : old)));
163
164 if (!list_empty(&df->unused)) {
165 de = list_entry(df->unused.next, struct tabdiff_entry, changes);
166 list_del(&de->changes);
167 } else {
168 de = calloc(1, sizeof(*de));
169 if (!de)
170 return -ENOMEM;
171 }
172
173 INIT_LIST_HEAD(&de->changes);
174
175 mnt_ref_fs(new);
176 mnt_ref_fs(old);
177
178 mnt_unref_fs(de->new_fs);
179 mnt_unref_fs(de->old_fs);
180
181 de->old_fs = old;
182 de->new_fs = new;
183 de->oper = oper;
184
185 list_add_tail(&de->changes, &df->changes);
186 df->nchanges++;
187 return 0;
188 }
189
190 static struct tabdiff_entry *tabdiff_get_mount(struct libmnt_tabdiff *df,
191 const char *src,
192 int id)
193 {
194 struct list_head *p;
195
196 assert(df);
197
198 list_for_each(p, &df->changes) {
199 struct tabdiff_entry *de;
200
201 de = list_entry(p, struct tabdiff_entry, changes);
202
203 if (de->oper == MNT_TABDIFF_MOUNT && de->new_fs &&
204 mnt_fs_get_id(de->new_fs) == id) {
205
206 const char *s = mnt_fs_get_source(de->new_fs);
207
208 if (s == NULL && src == NULL)
209 return de;
210 if (s && src && strcmp(s, src) == 0)
211 return de;
212 }
213 }
214 return NULL;
215 }
216
217 /**
218 * mnt_diff_tables:
219 * @df: diff handler
220 * @old_tab: old table
221 * @new_tab: new table
222 *
223 * Compares @old_tab and @new_tab, the result is stored in @df and accessible by
224 * mnt_tabdiff_next_change().
225 *
226 * Returns: number of changes, negative number in case of error.
227 */
228 int mnt_diff_tables(struct libmnt_tabdiff *df, struct libmnt_table *old_tab,
229 struct libmnt_table *new_tab)
230 {
231 struct libmnt_fs *fs;
232 struct libmnt_iter itr;
233 int no, nn;
234
235 if (!df || !old_tab || !new_tab)
236 return -EINVAL;
237
238 tabdiff_reset(df);
239
240 no = mnt_table_get_nents(old_tab);
241 nn = mnt_table_get_nents(new_tab);
242
243 if (!no && !nn) /* both tables are empty */
244 return 0;
245
246 DBG(DIFF, ul_debugobj(df, "analyze new (%d entries), "
247 "old (%d entries)",
248 nn, no));
249
250 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
251
252 /* all mounted or umounted */
253 if (!no && nn) {
254 while(mnt_table_next_fs(new_tab, &itr, &fs) == 0)
255 tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT);
256 goto done;
257
258 } else if (no && !nn) {
259 while(mnt_table_next_fs(old_tab, &itr, &fs) == 0)
260 tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT);
261 goto done;
262 }
263
264 /* search newly mounted or modified */
265 while(mnt_table_next_fs(new_tab, &itr, &fs) == 0) {
266 struct libmnt_fs *o_fs;
267 const char *src = mnt_fs_get_source(fs),
268 *tgt = mnt_fs_get_target(fs);
269
270 o_fs = mnt_table_find_pair(old_tab, src, tgt, MNT_ITER_FORWARD);
271 if (!o_fs)
272 /* 'fs' is not in the old table -- so newly mounted */
273 tabdiff_add_entry(df, NULL, fs, MNT_TABDIFF_MOUNT);
274 else {
275 /* is modified? */
276 const char *v1 = mnt_fs_get_vfs_options(o_fs),
277 *v2 = mnt_fs_get_vfs_options(fs),
278 *f1 = mnt_fs_get_fs_options(o_fs),
279 *f2 = mnt_fs_get_fs_options(fs);
280
281 if ((v1 && v2 && strcmp(v1, v2) != 0) || (f1 && f2 && strcmp(f1, f2) != 0))
282 tabdiff_add_entry(df, o_fs, fs, MNT_TABDIFF_REMOUNT);
283 }
284 }
285
286 /* search umounted or moved */
287 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
288 while(mnt_table_next_fs(old_tab, &itr, &fs) == 0) {
289 const char *src = mnt_fs_get_source(fs),
290 *tgt = mnt_fs_get_target(fs);
291
292 if (!mnt_table_find_pair(new_tab, src, tgt, MNT_ITER_FORWARD)) {
293 struct tabdiff_entry *de;
294
295 de = tabdiff_get_mount(df, src, mnt_fs_get_id(fs));
296 if (de) {
297 mnt_ref_fs(fs);
298 mnt_unref_fs(de->old_fs);
299 de->oper = MNT_TABDIFF_MOVE;
300 de->old_fs = fs;
301 } else
302 tabdiff_add_entry(df, fs, NULL, MNT_TABDIFF_UMOUNT);
303 }
304 }
305 done:
306 DBG(DIFF, ul_debugobj(df, "%d changes detected", df->nchanges));
307 return df->nchanges;
308 }
309
310 #ifdef TEST_PROGRAM
311
312 static int test_diff(struct libmnt_test *ts, int argc, char *argv[])
313 {
314 struct libmnt_table *tb_old, *tb_new;
315 struct libmnt_tabdiff *diff;
316 struct libmnt_iter *itr;
317 struct libmnt_fs *old, *new;
318 int rc = -1, change;
319
320 tb_old = mnt_new_table_from_file(argv[1]);
321 tb_new = mnt_new_table_from_file(argv[2]);
322 diff = mnt_new_tabdiff();
323 itr = mnt_new_iter(MNT_ITER_FORWARD);
324
325 if (!tb_old || !tb_new || !diff || !itr) {
326 warnx("failed to allocate resources");
327 goto done;
328 }
329
330 rc = mnt_diff_tables(diff, tb_old, tb_new);
331 if (rc < 0)
332 goto done;
333
334 while(mnt_tabdiff_next_change(diff, itr, &old, &new, &change) == 0) {
335
336 printf("%s on %s: ", mnt_fs_get_source(new ? new : old),
337 mnt_fs_get_target(new ? new : old));
338
339 switch(change) {
340 case MNT_TABDIFF_MOVE:
341 printf("MOVED to %s\n", mnt_fs_get_target(new));
342 break;
343 case MNT_TABDIFF_UMOUNT:
344 printf("UMOUNTED\n");
345 break;
346 case MNT_TABDIFF_REMOUNT:
347 printf("REMOUNTED from '%s' to '%s'\n",
348 mnt_fs_get_options(old),
349 mnt_fs_get_options(new));
350 break;
351 case MNT_TABDIFF_MOUNT:
352 printf("MOUNTED\n");
353 break;
354 default:
355 printf("unknown change!\n");
356 }
357 }
358
359 rc = 0;
360 done:
361 mnt_unref_table(tb_old);
362 mnt_unref_table(tb_new);
363 mnt_free_tabdiff(diff);
364 mnt_free_iter(itr);
365 return rc;
366 }
367
368 int main(int argc, char *argv[])
369 {
370 struct libmnt_test tss[] = {
371 { "--diff", test_diff, "<old> <new> prints change" },
372 { NULL }
373 };
374
375 return mnt_run_test(tss, argc, argv);
376 }
377
378 #endif /* TEST_PROGRAM */