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