1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
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, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <dbm_migration.h>
26
27 #include <lastseen.h>
28 #include <logging.h>
29
30 typedef struct
31 {
32 double q;
33 double expect;
34 double var;
35 } QPoint0;
36
37 #define QPOINT0_OFFSET 128
38
39 /*
40 * Structure of version 0 lastseen entry:
41 *
42 * flag | hostkey -> address | QPoint
43 * | | | \- 3*double
44 * | | \- 128 chars
45 * | \- N*chars
46 * \- 1 byte, '+' or '-'
47 */
48
LastseenMigrationVersion0(DBHandle * db)49 static bool LastseenMigrationVersion0(DBHandle *db)
50 {
51 bool errors = false;
52 DBCursor *cursor;
53
54 if (!NewDBCursor(db, &cursor))
55 {
56 return false;
57 }
58
59 char *key;
60 void *value;
61 int ksize, vsize;
62
63 while (NextDB(cursor, &key, &ksize, &value, &vsize))
64 {
65 if (ksize == 0)
66 {
67 Log(LOG_LEVEL_INFO, "LastseenMigrationVersion0: Database structure error -- zero-length key.");
68 continue;
69 }
70
71 /* Only look for old [+-]kH -> IP<QPoint> entries */
72 if ((key[0] != '+') && (key[0] != '-'))
73 {
74 /* Warn about completely unexpected keys */
75
76 if ((key[0] != 'q') && (key[0] != 'k') && (key[0] != 'a'))
77 {
78 Log(LOG_LEVEL_INFO, "LastseenMigrationVersion0: Malformed key found '%s'", key);
79 }
80
81 continue;
82 }
83
84 bool incoming = key[0] == '-';
85 const char *hostkey = key + 1;
86
87 /* Only migrate sane data */
88 if (vsize != QPOINT0_OFFSET + sizeof(QPoint0))
89 {
90 Log(LOG_LEVEL_INFO,
91 "LastseenMigrationVersion0: invalid value size for key '%s', entry is deleted",
92 key);
93 DBCursorDeleteEntry(cursor);
94 continue;
95 }
96
97 /* Properly align the data */
98 const char *old_data_address = (const char *) value;
99 QPoint0 old_data_q;
100 memcpy(&old_data_q, (const char *) value + QPOINT0_OFFSET,
101 sizeof(QPoint0));
102
103 char hostkey_key[CF_BUFSIZE];
104 snprintf(hostkey_key, CF_BUFSIZE, "k%s", hostkey);
105
106 if (!WriteDB(db, hostkey_key, old_data_address, strlen(old_data_address) + 1))
107 {
108 Log(LOG_LEVEL_INFO, "Unable to write version 1 lastseen entry for '%s'", key);
109 errors = true;
110 continue;
111 }
112
113 char address_key[CF_BUFSIZE];
114 snprintf(address_key, CF_BUFSIZE, "a%s", old_data_address);
115
116 if (!WriteDB(db, address_key, hostkey, strlen(hostkey) + 1))
117 {
118 Log(LOG_LEVEL_INFO, "Unable to write version 1 reverse lastseen entry for '%s'", key);
119 errors = true;
120 continue;
121 }
122
123 char quality_key[CF_BUFSIZE];
124 snprintf(quality_key, CF_BUFSIZE, "q%c%s", incoming ? 'i' : 'o', hostkey);
125
126 /*
127 Ignore malformed connection quality data
128 */
129
130 if ((!isfinite(old_data_q.q))
131 || (old_data_q.q < 0)
132 || (!isfinite(old_data_q.expect))
133 || (!isfinite(old_data_q.var)))
134 {
135 Log(LOG_LEVEL_INFO, "Ignoring malformed connection quality data for '%s'", key);
136 DBCursorDeleteEntry(cursor);
137 continue;
138 }
139
140 KeyHostSeen data = {
141 .lastseen = (time_t)old_data_q.q,
142 .Q = {
143 /*
144 Previously .q wasn't stored in database, but was calculated
145 every time as a difference between previous timestamp and a
146 new timestamp. Given we don't have this information during
147 the database upgrade, just assume that last reading is an
148 average one.
149 */
150 .q = old_data_q.expect,
151 .dq = 0,
152 .expect = old_data_q.expect,
153 .var = old_data_q.var,
154 }
155 };
156
157 if (!WriteDB(db, quality_key, &data, sizeof(data)))
158 {
159 Log(LOG_LEVEL_INFO, "Unable to write version 1 connection quality key for '%s'", key);
160 errors = true;
161 continue;
162 }
163
164 if (!DBCursorDeleteEntry(cursor))
165 {
166 Log(LOG_LEVEL_INFO, "Unable to delete version 0 lastseen entry for '%s'", key);
167 errors = true;
168 }
169 }
170
171 if (DeleteDBCursor(cursor) == false)
172 {
173 Log(LOG_LEVEL_ERR, "LastseenMigrationVersion0: Unable to close cursor");
174 errors = true;
175 }
176
177 if ((!errors) && (!WriteDB(db, "version", "1", sizeof("1"))))
178 {
179 errors = true;
180 }
181
182 return !errors;
183 }
184
185 const DBMigrationFunction dbm_migration_plan_lastseen[] =
186 {
187 LastseenMigrationVersion0,
188 NULL
189 };
190