]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/objectstore/chain_xattr.cc
update source to Ceph Pacific 16.2.2
[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 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
126 ASSERT_DEATH(chain_setxattr(file, name.c_str(), &x, sizeof(x)), "");
127 ASSERT_DEATH(chain_fsetxattr(fd, name.c_str(), &x, sizeof(x)), "");
128 ::testing::FLAGS_gtest_death_test_style = "fast";
129 }
130
131 {
132 const string name = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@');
133 const string x(LARGE_BLOCK_LEN, 'X');
134 {
135 char y[LARGE_BLOCK_LEN];
136 ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
137 ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, LARGE_BLOCK_LEN - 1));
138 ASSERT_EQ(-ERANGE, chain_getxattr(file, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
139 ASSERT_EQ(0, chain_removexattr(file, name.c_str()));
140 }
141
142 {
143 char y[LARGE_BLOCK_LEN];
144 ASSERT_EQ(LARGE_BLOCK_LEN, chain_fsetxattr(fd, name.c_str(), x.c_str(), LARGE_BLOCK_LEN));
145 ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, LARGE_BLOCK_LEN - 1));
146 ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, name.c_str(), y, CHAIN_XATTR_MAX_BLOCK_LEN));
147 ASSERT_EQ(0, chain_fremovexattr(fd, name.c_str()));
148 }
149 }
150
151 ::close(fd);
152 ::unlink(file);
153 }
154
155 TEST(chain_xattr, chunk_aligned) {
156 const char* file = FILENAME;
157 ::unlink(file);
158 int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700);
159 const string user("user.");
160
161 // set N* chunk size
162 const string name = "user.foo";
163 const string name2 = "user.bar";
164
165 for (int len = CHAIN_XATTR_MAX_BLOCK_LEN - 10;
166 len < CHAIN_XATTR_MAX_BLOCK_LEN + 10;
167 ++len) {
168 cout << len << std::endl;
169 const string x(len, 'x');
170 char buf[len*2];
171 ASSERT_EQ(len, chain_setxattr(file, name.c_str(), x.c_str(), len));
172 char attrbuf[4096];
173 int l = ceph_os_listxattr(file, attrbuf, sizeof(attrbuf));
174 for (char *p = attrbuf; p - attrbuf < l; p += strlen(p) + 1) {
175 cout << " attr " << p << std::endl;
176 }
177 ASSERT_EQ(len, chain_getxattr(file, name.c_str(), buf, len*2));
178 ASSERT_EQ(0, chain_removexattr(file, name.c_str()));
179
180 ASSERT_EQ(len, chain_fsetxattr(fd, name2.c_str(), x.c_str(), len));
181 l = ceph_os_flistxattr(fd, attrbuf, sizeof(attrbuf));
182 for (char *p = attrbuf; p - attrbuf < l; p += strlen(p) + 1) {
183 cout << " attr " << p << std::endl;
184 }
185 ASSERT_EQ(len, chain_fgetxattr(fd, name2.c_str(), buf, len*2));
186 ASSERT_EQ(0, chain_fremovexattr(fd, name2.c_str()));
187 }
188
189 for (int len = CHAIN_XATTR_SHORT_BLOCK_LEN - 10;
190 len < CHAIN_XATTR_SHORT_BLOCK_LEN + 10;
191 ++len) {
192 cout << len << std::endl;
193 const string x(len, 'x');
194 char buf[len*2];
195 ASSERT_EQ(len, chain_setxattr(file, name.c_str(), x.c_str(), len));
196 char attrbuf[4096];
197 int l = ceph_os_listxattr(file, attrbuf, sizeof(attrbuf));
198 for (char *p = attrbuf; p - attrbuf < l; p += strlen(p) + 1) {
199 cout << " attr " << p << std::endl;
200 }
201 ASSERT_EQ(len, chain_getxattr(file, name.c_str(), buf, len*2));
202 }
203
204 {
205 // test tail path in chain_getxattr
206 const char *aname = "user.baz";
207 char buf[CHAIN_XATTR_SHORT_BLOCK_LEN*3];
208 memset(buf, 'x', sizeof(buf));
209 ASSERT_EQ((int)sizeof(buf), chain_setxattr(file, aname, buf, sizeof(buf)));
210 ASSERT_EQ(-ERANGE, chain_getxattr(file, aname, buf,
211 CHAIN_XATTR_SHORT_BLOCK_LEN*2));
212 }
213 {
214 // test tail path in chain_fgetxattr
215 const char *aname = "user.biz";
216 char buf[CHAIN_XATTR_SHORT_BLOCK_LEN*3];
217 memset(buf, 'x', sizeof(buf));
218 ASSERT_EQ((int)sizeof(buf), chain_fsetxattr(fd, aname, buf, sizeof(buf)));
219 ASSERT_EQ(-ERANGE, chain_fgetxattr(fd, aname, buf,
220 CHAIN_XATTR_SHORT_BLOCK_LEN*2));
221 }
222
223 ::close(fd);
224 ::unlink(file);
225 }
226
227 void get_vector_from_xattr(vector<string> &xattrs, char* xattr, int size) {
228 char *end = xattr + size;
229 while (xattr < end) {
230 if (*xattr == '\0' )
231 break;
232 xattrs.push_back(xattr);
233 xattr += strlen(xattr) + 1;
234 }
235 }
236
237 bool listxattr_cmp(char* xattr1, char* xattr2, int size) {
238 vector<string> xattrs1;
239 vector<string> xattrs2;
240 get_vector_from_xattr(xattrs1, xattr1, size);
241 get_vector_from_xattr(xattrs2, xattr2, size);
242
243 if (xattrs1.size() != xattrs2.size())
244 return false;
245
246 std::sort(xattrs1.begin(), xattrs1.end());
247 std::sort(xattrs2.begin(), xattrs2.end());
248 std::vector<string> diff;
249 std::set_difference(xattrs1.begin(), xattrs1.end(),
250 xattrs2.begin(), xattrs2.end(),
251 diff.begin());
252
253 return diff.empty();
254 }
255
256 TEST(chain_xattr, listxattr) {
257 const char* file = FILENAME;
258 ::unlink(file);
259 int fd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700);
260 const string user("user.");
261 const string name1 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '1');
262 const string name2 = user + string(CHAIN_XATTR_MAX_NAME_LEN - user.size(), '@');
263 const string x(LARGE_BLOCK_LEN, 'X');
264 const int y = 1234;
265
266 int orig_size = chain_listxattr(file, NULL, 0);
267 char *orig_buffer = NULL;
268 string orig_str;
269 if (orig_size) {
270 orig_buffer = (char*)malloc(orig_size);
271 chain_flistxattr(fd, orig_buffer, orig_size);
272 orig_str = string(orig_buffer);
273 orig_size = orig_str.size();
274 }
275
276 ASSERT_EQ(LARGE_BLOCK_LEN, chain_setxattr(file, name1.c_str(), x.c_str(), LARGE_BLOCK_LEN));
277 ASSERT_EQ((int)sizeof(y), chain_setxattr(file, name2.c_str(), &y, sizeof(y)));
278
279 int buffer_size = 0;
280 if (orig_size)
281 buffer_size += orig_size + sizeof(char);
282 buffer_size += name1.size() + sizeof(char) + name2.size() + sizeof(char);
283
284 int index = 0;
285 char* expected = (char*)malloc(buffer_size);
286 ::memset(expected, '\0', buffer_size);
287 if (orig_size) {
288 ::strcpy(expected, orig_str.c_str());
289 index = orig_size + 1;
290 }
291 ::strcpy(expected + index, name1.c_str());
292 ::strcpy(expected + index + name1.size() + 1, name2.c_str());
293 char* actual = (char*)malloc(buffer_size);
294 ::memset(actual, '\0', buffer_size);
295 ASSERT_LT(buffer_size, chain_listxattr(file, NULL, 0)); // size evaluation is conservative
296 chain_listxattr(file, actual, buffer_size);
297 ASSERT_TRUE(listxattr_cmp(expected, actual, buffer_size));
298 ::memset(actual, '\0', buffer_size);
299 chain_flistxattr(fd, actual, buffer_size);
300 ASSERT_TRUE(listxattr_cmp(expected, actual, buffer_size));
301
302 int unlikely_to_be_a_valid_fd = 400;
303 ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, 0));
304 ASSERT_GT(0, chain_listxattr("UNLIKELY_TO_EXIST", actual, buffer_size));
305 ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, 0));
306 ASSERT_GT(0, chain_flistxattr(unlikely_to_be_a_valid_fd, actual, buffer_size));
307 ASSERT_EQ(-ERANGE, chain_listxattr(file, actual, 1));
308 ASSERT_EQ(-ERANGE, chain_flistxattr(fd, actual, 1));
309
310 ASSERT_EQ(0, chain_removexattr(file, name1.c_str()));
311 ASSERT_EQ(0, chain_removexattr(file, name2.c_str()));
312
313 free(orig_buffer);
314 free(actual);
315 free(expected);
316 ::unlink(file);
317 }
318
319 list<string> get_xattrs(int fd)
320 {
321 char _buf[1024];
322 char *buf = _buf;
323 int len = sys_flistxattr(fd, _buf, sizeof(_buf));
324 if (len < 0)
325 return list<string>();
326 list<string> ret;
327 while (len > 0) {
328 size_t next_len = strlen(buf);
329 ret.push_back(string(buf, buf + next_len));
330 ceph_assert(len >= (int)(next_len + 1));
331 buf += (next_len + 1);
332 len -= (next_len + 1);
333 }
334 return ret;
335 }
336
337 list<string> get_xattrs(string fn)
338 {
339 int fd = ::open(fn.c_str(), O_RDONLY);
340 if (fd < 0)
341 return list<string>();
342 auto ret = get_xattrs(fd);
343 ::close(fd);
344 return ret;
345 }
346
347 TEST(chain_xattr, fskip_chain_cleanup_and_ensure_single_attr)
348 {
349 const char *name = "user.foo";
350 const char *file = FILENAME;
351 ::unlink(file);
352 int fd = ::open(file, O_CREAT|O_RDWR|O_TRUNC, 0700);
353 ceph_assert(fd >= 0);
354
355 std::size_t existing_xattrs = get_xattrs(fd).size();
356 char buf[800];
357 memset(buf, 0x1F, sizeof(buf));
358 // set chunked without either
359 {
360 std::size_t r = chain_fsetxattr(fd, name, buf, sizeof(buf));
361 ASSERT_EQ(sizeof(buf), r);
362 ASSERT_GT(get_xattrs(fd).size(), existing_xattrs + 1UL);
363 }
364
365 // verify
366 {
367 char buf2[sizeof(buf)*2];
368 std::size_t r = chain_fgetxattr(fd, name, buf2, sizeof(buf2));
369 ASSERT_EQ(sizeof(buf), r);
370 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
371 }
372
373 // overwrite
374 {
375 std::size_t r = chain_fsetxattr<false, true>(fd, name, buf, sizeof(buf));
376 ASSERT_EQ(sizeof(buf), r);
377 ASSERT_EQ(existing_xattrs + 1UL, get_xattrs(fd).size());
378 }
379
380 // verify
381 {
382 char buf2[sizeof(buf)*2];
383 std::size_t r = chain_fgetxattr(fd, name, buf2, sizeof(buf2));
384 ASSERT_EQ(sizeof(buf), r);
385 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
386 }
387
388 ::close(fd);
389 ::unlink(file);
390 }
391
392 TEST(chain_xattr, skip_chain_cleanup_and_ensure_single_attr)
393 {
394 const char *name = "user.foo";
395 const char *file = FILENAME;
396 ::unlink(file);
397 int fd = ::open(file, O_CREAT|O_RDWR|O_TRUNC, 0700);
398 ceph_assert(fd >= 0);
399 std::size_t existing_xattrs = get_xattrs(fd).size();
400 ::close(fd);
401
402 char buf[3000];
403 memset(buf, 0x1F, sizeof(buf));
404 // set chunked without either
405 {
406 std::size_t r = chain_setxattr(file, name, buf, sizeof(buf));
407 ASSERT_EQ(sizeof(buf), r);
408 ASSERT_GT(get_xattrs(file).size(), existing_xattrs + 1UL);
409 }
410
411 // verify
412 {
413 char buf2[sizeof(buf)*2];
414 std::size_t r = chain_getxattr(file, name, buf2, sizeof(buf2));
415 ASSERT_EQ(sizeof(buf), r);
416 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
417 }
418
419 // overwrite
420 {
421 std::size_t r = chain_setxattr<false, true>(file, name, buf, sizeof(buf));
422 ASSERT_EQ(sizeof(buf), r);
423 ASSERT_EQ(existing_xattrs + 1UL, get_xattrs(file).size());
424 }
425
426 // verify
427 {
428 char buf2[sizeof(buf)*2];
429 std::size_t r = chain_getxattr(file, name, buf2, sizeof(buf2));
430 ASSERT_EQ(sizeof(buf), r);
431 ASSERT_EQ(0, memcmp(buf, buf2, sizeof(buf)));
432 }
433
434 ::unlink(file);
435 }
436
437 int main(int argc, char **argv) {
438 vector<const char*> args;
439 argv_to_vec(argc, (const char **)argv, args);
440
441 auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
442 CODE_ENVIRONMENT_UTILITY,
443 CINIT_FLAG_NO_DEFAULT_CONFIG_FILE);
444 common_init_finish(g_ceph_context);
445 g_ceph_context->_conf.set_val("err_to_stderr", "false");
446 g_ceph_context->_conf.set_val("log_to_stderr", "false");
447 g_ceph_context->_conf.apply_changes(nullptr);
448
449 const char* file = FILENAME;
450 int x = 1234;
451 int y = 0;
452 int tmpfd = ::open(file, O_CREAT|O_WRONLY|O_TRUNC, 0700);
453 int ret = ::ceph_os_fsetxattr(tmpfd, "user.test", &x, sizeof(x));
454 if (ret >= 0)
455 ret = ::ceph_os_fgetxattr(tmpfd, "user.test", &y, sizeof(y));
456 ::close(tmpfd);
457 ::unlink(file);
458 if ((ret < 0) || (x != y)) {
459 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;
460 } else {
461 ::testing::InitGoogleTest(&argc, argv);
462 return RUN_ALL_TESTS();
463 }
464 }
465
466 // Local Variables:
467 // compile-command: "cd ../.. ; make unittest_chain_xattr ; valgrind --tool=memcheck ./unittest_chain_xattr # --gtest_filter=chain_xattr.get_and_set"
468 // End: