@@ -56,21 +56,24 @@ typedef struct zfs_zstd_header {
56
56
57
57
/*
58
58
* Version and compression level
59
- * We use a union to be able to big endian encode a single 32 bit
60
- * unsigned integer, but still access the individual bitmasked
61
- * components easily.
59
+ * We used to use a union to reference compression level
60
+ * and version easily, but as it turns out, relying on the
61
+ * ordering of bitfields is not remotely portable.
62
+ * So now we have get/set functions in zfs_zstd.c for
63
+ * manipulating this in just the right way forever.
62
64
*/
63
- union {
64
- uint32_t raw_version_level ;
65
- struct {
66
- uint32_t version : 24 ;
67
- uint8_t level ;
68
- };
69
- };
70
-
65
+ uint32_t raw_version_level ;
71
66
char data [];
72
67
} zfs_zstdhdr_t ;
73
68
69
+ /*
70
+ * Simple struct to pass the data from raw_version_level around.
71
+ */
72
+ typedef struct zfs_zstd_meta {
73
+ uint8_t level ;
74
+ uint32_t version ;
75
+ } zfs_zstdmeta_t ;
76
+
74
77
/*
75
78
* kstat helper macros
76
79
*/
@@ -94,6 +97,129 @@ int zfs_zstd_decompress(void *s_start, void *d_start, size_t s_len,
94
97
size_t d_len , int n );
95
98
void zfs_zstd_cache_reap_now (void );
96
99
100
+ /*
101
+ * So, the reason we have all these complicated set/get functions is that
102
+ * originally, in the zstd "header" we wrote out to disk, we used a 32-bit
103
+ * bitfield to store the "level" (8 bits) and "version" (24 bits).
104
+ *
105
+ * Unfortunately, bitfields make few promises about how they're arranged in
106
+ * memory...
107
+ *
108
+ * By way of example, if we were using version 1.4.5 and level 3, it'd be
109
+ * level = 0x03, version = 10405/0x0028A5, which gets broken into Vhigh = 0x00,
110
+ * Vmid = 0x28, Vlow = 0xA5. We include these positions below to help follow
111
+ * which data winds up where.
112
+ *
113
+ * As a consequence, we wound up with little endian platforms with a layout
114
+ * like this in memory:
115
+ *
116
+ * 0 8 16 24 32
117
+ * +-------+-------+-------+-------+
118
+ * | Vlow | Vmid | Vhigh | level |
119
+ * +-------+-------+-------+-------+
120
+ * =A5 =28 =00 =03
121
+ *
122
+ * ...and then, after being run through BE_32(), serializing this out to
123
+ * disk:
124
+ *
125
+ * 0 8 16 24 32
126
+ * +-------+-------+-------+-------+
127
+ * | level | Vhigh | Vmid | Vlow |
128
+ * +-------+-------+-------+-------+
129
+ * =03 =00 =28 =A5
130
+ *
131
+ * while on big-endian systems, since BE_32() is a noop there, both in
132
+ * memory and on disk, we wind up with:
133
+ *
134
+ * 0 8 16 24 32
135
+ * +-------+-------+-------+-------+
136
+ * | Vhigh | Vmid | Vlow | level |
137
+ * +-------+-------+-------+-------+
138
+ * =00 =28 =A5 =03
139
+ *
140
+ * (Vhigh is always 0 until version exceeds 6.55.35. Vmid and Vlow are the
141
+ * other two bytes of the "version" data.)
142
+ *
143
+ * So now we use the BF32_SET macros to get consistent behavior (the
144
+ * ondisk LE encoding, since x86 currently rules the world) across
145
+ * platforms, but the "get" behavior requires that we check each of the
146
+ * bytes in the aforementioned former-bitfield for 0x00, and from there,
147
+ * we can know which possible layout we're dealing with. (Only the two
148
+ * that have been observed in the wild are illustrated above, but handlers
149
+ * for all 4 positions of 0x00 are implemented.
150
+ */
151
+
152
+ static inline void
153
+ zfs_get_hdrmeta (const zfs_zstdhdr_t * blob , zfs_zstdmeta_t * res )
154
+ {
155
+ uint32_t raw = blob -> raw_version_level ;
156
+ uint8_t findme = 0xff ;
157
+ int shift ;
158
+ for (shift = 0 ; shift < 4 ; shift ++ ) {
159
+ findme = BF32_GET (raw , 8 * shift , 8 );
160
+ if (findme == 0 )
161
+ break ;
162
+ }
163
+ switch (shift ) {
164
+ case 0 :
165
+ res -> level = BF32_GET (raw , 24 , 8 );
166
+ res -> version = BSWAP_32 (raw );
167
+ res -> version = BF32_GET (res -> version , 8 , 24 );
168
+ break ;
169
+ case 1 :
170
+ res -> level = BF32_GET (raw , 0 , 8 );
171
+ res -> version = BSWAP_32 (raw );
172
+ res -> version = BF32_GET (res -> version , 0 , 24 );
173
+ break ;
174
+ case 2 :
175
+ res -> level = BF32_GET (raw , 24 , 8 );
176
+ res -> version = BF32_GET (raw , 0 , 24 );
177
+ break ;
178
+ case 3 :
179
+ res -> level = BF32_GET (raw , 0 , 8 );
180
+ res -> version = BF32_GET (raw , 8 , 24 );
181
+ break ;
182
+ default :
183
+ res -> level = 0 ;
184
+ res -> version = 0 ;
185
+ break ;
186
+ }
187
+ }
188
+
189
+ static inline uint8_t
190
+ zfs_get_hdrlevel (const zfs_zstdhdr_t * blob )
191
+ {
192
+ uint8_t level = 0 ;
193
+ zfs_zstdmeta_t res ;
194
+ zfs_get_hdrmeta (blob , & res );
195
+ level = res .level ;
196
+ return (level );
197
+ }
198
+
199
+ static inline uint32_t
200
+ zfs_get_hdrversion (const zfs_zstdhdr_t * blob )
201
+ {
202
+ uint32_t version = 0 ;
203
+ zfs_zstdmeta_t res ;
204
+ zfs_get_hdrmeta (blob , & res );
205
+ version = res .version ;
206
+ return (version );
207
+
208
+ }
209
+
210
+ static inline void
211
+ zfs_set_hdrversion (zfs_zstdhdr_t * blob , uint32_t version )
212
+ {
213
+ BF32_SET (blob -> raw_version_level , 0 , 24 , version );
214
+ }
215
+
216
+ static inline void
217
+ zfs_set_hdrlevel (zfs_zstdhdr_t * blob , uint8_t level )
218
+ {
219
+ BF32_SET (blob -> raw_version_level , 24 , 8 , level );
220
+ }
221
+
222
+
97
223
#ifdef __cplusplus
98
224
}
99
225
#endif
0 commit comments