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