]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/objectstore/chain_xattr.cc
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / src / test / objectstore / chain_xattr.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com>
7 *
8 * Author: Loic Dachary <loic@dachary.org>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Library Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Library Public License for more details.
19 *
20 */
21
22 #include <stdio.h>
23 #include <signal.h>
24 #include "os/filestore/chain_xattr.h"
25 #include "include/Context.h"
26 #include "include/coredumpctl.h"
27 #include "common/errno.h"
28 #include "common/ceph_argparse.h"
29 #include "global/global_init.h"
30 #include <gtest/gtest.h>
31
32 #define LARGE_BLOCK_LEN CHAIN_XATTR_MAX_BLOCK_LEN + 1024
33 #define FILENAME "chain_xattr"
34
35 TEST(chain_xattr, get_and_set) {
36 const char* file = FILENAME;
37 ::unlink(file);
38 int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700);
39 const string user("user.");
40
41 {
42 const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@');
43 const string x(LARGE_BLOCK_LEN, 'X');
44
45 {
46 char y[LARGE_BLOCK_LEN];
47 ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
48 ASSERT_EQ(LARGE_BLOCK_LEN, chain_getxattr(file, name.c_str(), 0, 0));
49 ASSERT_EQ(LARGE_BLOCK_LEN, chain_getxattr(file, name.c_str(), y, LARGE_BLOCK_LEN));
50 ASSERT_EQ(0, chain_removexattr(file, name.c_str()));
51 ASSERT_EQ(0, memcmp(x.c_str(), y, LARGE_BLOCK_LEN));
52 }
53
54 {
55 char y[LARGE_BLOCK_LEN];
56 ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
57 ASSERT_EQ(LARGE_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), 0, 0));
58 ASSERT_EQ(LARGE_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), y, LARGE_BLOCK_LEN));
59 ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str()));
60 ASSERT_EQ(0, memcmp(x.c_str(), y, LARGE_BLOCK_LEN));
61 }
62 }
63
64 //
65 // when chain_setxattr is used to store value that is
66 // CHAIN_XATTR_MAX_BLOCK_LEN * 2 + 10 bytes long it
67 //
68 // add user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes
69 // add user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes
70 // add user.foo@2 => 10 bytes
71 //
72 // then ( no chain_removexattr in between ) when it is used to
73 // override with a value that is exactly CHAIN_XATTR_MAX_BLOCK_LEN
74 // bytes long it will
75 //
76 // replace user.foo => CHAIN_XATTR_MAX_BLOCK_LEN bytes
77 // remove user.foo@1 => CHAIN_XATTR_MAX_BLOCK_LEN bytes
78 // leak user.foo@2 => 10 bytes
79 //
80 // see http://marc.info/?l=ceph-devel&m=136027076615853&w=4 for the
81 // discussion
82 //
83 {
84 const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@');
85 const string x(LARGE_BLOCK_LEN, 'X');
86
87 {
88 char y[CHAIN_XATTR_MAX_BLOCK_LEN];
89 ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
90 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN));
91 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_getxattr(file, name.c_str(), 0, 0));
92 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_getxattr(file, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
93 ASSERT_EQ(0, chain_removexattr(file, name.c_str()));
94 ASSERT_EQ(0, memcmp(x.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
95 }
96
97 {
98 char y[CHAIN_XATTR_MAX_BLOCK_LEN];
99 ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
100 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), CHAIN_XATTR_MAX_BLOCK_LEN));
101 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), 0, 0));
102 ASSERT_EQ(CHAIN_XATTR_MAX_BLOCK_LEN, chain_fgetxattr(fd, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
103 ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str()));
104 ASSERT_EQ(0, memcmp(x.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
105 }
106 }
107
108 {
109 int x = 0;
110 ASSERT_EQ(-ENOENT, chain_setxattr("UNLIKELY_TO_EXIST", "NAME", &x, sizeof(x)));
111 ASSERT_EQ(-ENOENT, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", 0, 0));
112 ASSERT_EQ(-ENOENT, chain_getxattr("UNLIKELY_TO_EXIST", "NAME", &x, sizeof(x)));
113 ASSERT_EQ(-ENOENT, chain_removexattr("UNLIKELY_TO_EXIST", "NAME"));
114 int unlikely_to_be_a_valid_fd = 400;
115 ASSERT_EQ(-EBADF, chain_fsetxattr(unlikely_to_be_a_valid_fd, "NAME", &x, sizeof(x)));
116 ASSERT_EQ(-EBADF, chain_fgetxattr(unlikely_to_be_a_valid_fd, "NAME", 0, 0));
117 ASSERT_EQ(-EBADF, chain_fgetxattr(unlikely_to_be_a_valid_fd, "NAME", &x, sizeof(x)));
118 ASSERT_EQ(-EBADF, chain_fremovexattr(unlikely_to_be_a_valid_fd, "NAME"));
119 }
120
121 {
122 int x;
123 const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN * 2, '@');
124 PrCtl unset_dumpable;
125 ASSERT_DEATH(chain_setxattr(file, name.c_str(), &x, sizeof(x)), "");
126 ASSERT_DEATH(chain_fsetxattr(fd, name.c_str(), &x, sizeof(x)), "");
127 }
128
129 {
130 const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@');
131 const string x(LARGE_BLOCK_LEN, 'X');
132 {
133 char y[LARGE_BLOCK_LEN];
134 ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
135 ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, LARGE_BLOCK_LEN - 1));
136 ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
137 ASSERT_EQ(0, chain_removexattr(file, name.c_str()));
138 }
139
140 {
141 char y[LARGE_BLOCK_LEN];
142 ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
143 ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, LARGE_BLOCK_LEN - 1));
144 ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
145 ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str()));
146 }
147 }
148
149 ::close(fd);
150 ::unlink(file);
151 }
152
153 TEST(chain_xattr, chunk_aligned) {
154 const char* file = FILENAME;
155 ::unlink(file);
156 int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700);
157 const string user("user.");
158
159 // set N* chunk size
160 const string name = "user.foo";
161 const string name2 = "user.bar";
162
163 for (int len = CHAIN_XATTR_MAX_BLOCK_LEN - 10;
164 len < CHAIN_XATTR_MAX_BLOCK_LEN + 10;
165 ++len) {
166 cout << len << std::endl;
167 const string x(len, 'x');
168 char buf[len*2];
169 ASSERT_EQ(len, chain_setxattr(file, name.c_str(), x.c_str(), len));
170 char attrbuf[4096];
171 int l = ceph_os_listxattr(file, attrbuf, sizeof(attrbuf));
172 for (char *p = attrbuf; p - attrbuf < l; p += strlen(p) + 1) {
173 cout << " attr " << p << std::endl;
174 }
175 ASSERT_EQ(len, chain_getxattr(file, name.c_str(), buf, len*2));
176 ASSERT_EQ(0, chain_removexattr(file, name.c_str()));
177
178 ASSERT_EQ(len, chain_fsetxattr(fd, name2.c_str(), x.c_str(), len));
179 l = ceph_os_flistxattr(fd, attrbuf, sizeof(attrbuf));
180 for (char *p = attrbuf; p - attrbuf < l; p += strlen(p) + 1) {
181 cout << " attr " << p << std::endl;
182 }
183 ASSERT_EQ(len, chain_fgetxattr(fd, name2.c_str(), buf, len*2));
184 ASSERT_EQ(0, chain_fremovexattr(fd, name2.c_str()));
185 }
186
187 for (int len = CHAIN_XATTR_SHORT_BLOCK_LEN - 10;
188 len < CHAIN_XATTR_SHORT_BLOCK_LEN + 10;
189 ++len) {
190 cout << len << std::endl;
191 const string x(len, 'x');
192 char buf[len*2];
193 ASSERT_EQ(len, chain_setxattr(file, name.c_str(), x.c_str(), len));
194 char attrbuf[4096];
195 int l = ceph_os_listxattr(file, attrbuf, sizeof(attrbuf));
196 for (char *p = attrbuf; p - attrbuf < l; p += strlen(p) + 1) {
197 cout << " attr " << p << std::endl;
198 }
199 ASSERT_EQ(len, chain_getxattr(file, name.c_str(), buf, len*2));
200 }
201
202 {
203 // test tail path in chain_getxattr
204 const char *aname = "user.baz";
205 char buf[CHAIN_XATTR_SHORT_BLOCK_LEN*3];
206 memset(buf, 'x', sizeof(buf));
207 ASSERT_EQ((int)sizeof(buf), chain_setxattr(file, aname, buf, sizeof(buf)));
208 ASSERT_EQ(-ERANGE, chain_getxattr(file, aname, buf,
209 CHAIN_XATTR_SHORT_BLOCK_LEN*2));
210 }
211 {
212 // test tail path in chain_fgetxattr
213 const char *aname = "user.biz";
214 char buf[CHAIN_XATTR_SHORT_BLOCK_LEN*3];
215 memset(buf, 'x', sizeof(buf));
216 ASSERT_EQ((int)sizeof(buf), chain_fsetxattr(fd, aname, buf, sizeof(buf)));
217 ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, aname, buf,
218 CHAIN_XATTR_SHORT_BLOCK_LEN*2));
219 }
220
221 ::close(fd);
222 ::unlink(file);
223 }
224
225 void get_vector_from_xattr(vector<string> &xattrs, char* xattr, int size) {
226 char *end = xattr + size;
227 while (xattr < end) {
228 if (*xattr == '\0' )
229 break;
230 xattrs.push_back(xattr);
231 xattr += strlen(xattr) + 1;
232 }
233 }
234
235 bool listxattr_cmp(char* xattr1, char* xattr2, int size) {
236 vector<string> xattrs1;
237 vector<string> xattrs2;
238 get_vector_from_xattr(xattrs1, xattr1, size);
239 get_vector_from_xattr(xattrs2, xattr2, size);
240
241 if (xattrs1.size() != xattrs2.size())
242 return false;
243
244 std::sort(xattrs1.begin(), xattrs1.end());
245 std::sort(xattrs2.begin(), xattrs2.end());
246 std::vector<string> diff;
247 std::set_difference(xattrs1.begin(), xattrs1.end(),
248 xattrs2.begin(), xattrs2.end(),
249 diff.begin());
250
251 return diff.empty();
252 }
253
254 TEST(chain_xattr, listxattr) {
255 const char* file = FILENAME;
256 ::unlink(file);
257 int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700);
258 const string user("user.");
259 const string name1 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '1');
260 const string name2 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@');
261 const string x(LARGE_BLOCK_LEN, 'X');
262 const int y = 1234;
263
264 int orig_size = chain_listxattr(file, NULL, 0);
265 char *orig_buffer = NULL;
266 string orig_str;
267 if (orig_size) {
268 orig_buffer = (char*)malloc(orig_size);
269 chain_flistxattr(fd, orig_buffer, orig_size);
270 orig_str = string(orig_buffer);
271 orig_size = orig_str.size();
272 }
273
274 ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name1.c_str(), x.c_str(), LARGE_BLOCK_LEN));
275 ASSERT_EQ((int)sizeof(y), chain_setxattr(file, name2.c_str(), &y, sizeof(y)));
276
277 int buffer_size = 0;
278 if (orig_size)
279 buffer_size += orig_size + sizeof(char);
280 buffer_size += name1.size() + sizeof(char) + name2.size() + sizeof(char);
281
282 int index = 0;
283 char* expected = (char*)malloc(buffer_size);
284 ::memset(expected, '\0', buffer_size);
285 if (orig_size) {
286 ::strcpy(expected, orig_str.c_str());
287 index = orig_size + 1;
288 }
289 ::strcpy(expected + index, name1.c_str());
290 ::strcpy(expected + index + name1.size() + 1, name2.c_str());
291 char* actual = (char*)malloc(buffer_size);
292 ::memset(actual, '\0', buffer_size);
293 ASSERT_LT(buffer_size, chain_listxattr(file, NULL, 0)); // size evaluation is conservative
294 chain_listxattr(file, actual, buffer_size);
295 ASSERT_TRUE(listxattr_cmp(expected, actual, buffer_size));
296 ::memset(actual, '\0', buffer_size);
297 chain_flistxattr(fd, actual, buffer_size);
298 ASSERT_TRUE(listxattr_cmp(expected, actual, buffer_size));
299
300 int unlikely_to_be_a_valid_fd = 400;
301 ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, 0));
302 ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, buffer_size));
303 ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, 0));
304 ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, buffer_size));
305 ASSERT_EQ(-ERANGE, chain_listxattr(file, actual, 1));
306 ASSERT_EQ(-ERANGE, chain_flistxattr(fd, actual, 1));
307
308 ASSERT_EQ(0, chain_removexattr(file, name1.c_str()));
309 ASSERT_EQ(0, chain_removexattr(file, name2.c_str()));
310
311 free(orig_buffer);
312 free(actual);
313 free(expected);
314 ::unlink(file);
315 }
316
317 list<string> get_xattrs(int fd)
318 {
319 char _buf[1024];
320 char *buf = _buf;
321 int len = sys_flistxattr(fd, _buf, sizeof(_buf));
322 if (len < 0)
323 return list<string>();
324 list<string> ret;
325 while (len > 0) {
326 size_t next_len = strlen(buf);
327 ret.push_back(string(buf, buf + next_len));
328 assert(len >= (int)(next_len + 1));
329 buf += (next_len + 1);
330 len -= (next_len + 1);
331 }
332 return ret;
333 }
334
335 list<string> get_xattrs(string fn)
336 {
337 int fd = ::open(fn.c_str(), O_RDONLY);
338 if (fd < 0)
339 return list<string>();
340 auto ret = get_xattrs(fd);
341 ::close(fd);
342 return ret;
343 }
344
345 TEST(chain_xattr, fskip_chain_cleanup_and_ensure_single_attr)
346 {
347 const char *name = "user.foo";
348 const char *file = FILENAME;
349 ::unlink(file);
350 int fd = ::open(file, O_CREAT|O_RDWR|O_TRUNC, 0700);
351
352 std::size_t existing_xattrs = get_xattrs(fd).size();
353 char buf[800];
354 memset(buf, 0x1F, sizeof(buf));
355 // set chunked without either
356 {
357 std::size_t r = chain_fsetxattr(fd, name, buf, sizeof(buf));
358 ASSERT_EQ(sizeof(buf), r);
359 ASSERT_GT(get_xattrs(fd).size(), existing_xattrs + 1UL);
360 }
361
362 // verify
363 {
364 char buf2[sizeof(buf)*2];
365 std::size_t r = chain_fgetxattr(fd, name, buf2, sizeof(buf2));
366 ASSERT_EQ(sizeof(buf), r);
367 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
368 }
369
370 // overwrite
371 {
372 std::size_t r = chain_fsetxattr<false, true>(fd, name, buf, sizeof(buf));
373 ASSERT_EQ(sizeof(buf), r);
374 ASSERT_EQ(existing_xattrs + 1UL, get_xattrs(fd).size());
375 }
376
377 // verify
378 {
379 char buf2[sizeof(buf)*2];
380 std::size_t r = chain_fgetxattr(fd, name, buf2, sizeof(buf2));
381 ASSERT_EQ(sizeof(buf), r);
382 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
383 }
384
385 ::close(fd);
386 ::unlink(file);
387 }
388
389 TEST(chain_xattr, skip_chain_cleanup_and_ensure_single_attr)
390 {
391 const char *name = "user.foo";
392 const char *file = FILENAME;
393 ::unlink(file);
394 int fd = ::open(file, O_CREAT|O_RDWR|O_TRUNC, 0700);
395 std::size_t existing_xattrs = get_xattrs(fd).size();
396 ::close(fd);
397
398 char buf[3000];
399 memset(buf, 0x1F, sizeof(buf));
400 // set chunked without either
401 {
402 std::size_t r = chain_setxattr(file, name, buf, sizeof(buf));
403 ASSERT_EQ(sizeof(buf), r);
404 ASSERT_GT(get_xattrs(file).size(), existing_xattrs + 1UL);
405 }
406
407 // verify
408 {
409 char buf2[sizeof(buf)*2];
410 std::size_t r = chain_getxattr(file, name, buf2, sizeof(buf2));
411 ASSERT_EQ(sizeof(buf), r);
412 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
413 }
414
415 // overwrite
416 {
417 std::size_t r = chain_setxattr<false, true>(file, name, buf, sizeof(buf));
418 ASSERT_EQ(sizeof(buf), r);
419 ASSERT_EQ(existing_xattrs + 1UL, get_xattrs(file).size());
420 }
421
422 // verify
423 {
424 char buf2[sizeof(buf)*2];
425 std::size_t r = chain_getxattr(file, name, buf2, sizeof(buf2));
426 ASSERT_EQ(sizeof(buf), r);
427 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
428 }
429
430 ::unlink(file);
431 }
432
433 int main(int argc, char **argv) {
434 vector<const char*> args;
435 argv_to_vec(argc, (const char **)argv, args);
436
437 auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
438 CODE_ENVIRONMENT_UTILITY, 0);
439 common_init_finish(g_ceph_context);
440 g_ceph_context->_conf->set_val("err_to_stderr", "false");
441 g_ceph_context->_conf->set_val("log_to_stderr", "false");
442 g_ceph_context->_conf->apply_changes(NULL);
443
444 const char* file = FILENAME;
445 int x = 1234;
446 int y = 0;
447 int tmpfd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700);
448 int ret = ::ceph_os_fsetxattr(tmpfd, "user.test", &x, sizeof(x));
449 if (ret >= 0)
450 ret = ::ceph_os_fgetxattr(tmpfd, "user.test", &y, sizeof(y));
451 ::close(tmpfd);
452 ::unlink(file);
453 if ((ret < 0) || (x != y)) {
454 cerr << "SKIP all tests because extended attributes don't appear to work in the file system in which the tests are run: " << cpp_strerror(ret) << std::endl;
455 } else {
456 ::testing::InitGoogleTest(&argc, argv);
457 return RUN_ALL_TESTS();
458 }
459 }
460
461 // Local Variables:
462 // compile-command: "cd ../.. ; make unittest_chain_xattr ; valgrind --tool=memcheck ./unittest_chain_xattr # --gtest_filter=chain_xattr.get_and_set"
463 // End: