1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com>
8 * Author: Loic Dachary <loic@dachary.org>
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)
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.
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>
32 #define LARGE_BLOCK_LEN CHAIN_XATTR_MAX_BLOCK_LEN + 1024
33 #define FILENAME "chain_xattr"
35 TEST(chain_xattr
, get_and_set
) {
36 const char* file
= FILENAME
;
38 int fd
= ::open(file
, O_CREAT
|O_WRONLY
|O_TRUNC
, 0700);
39 const string
user("user.");
42 const string name
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '@');
43 const string
x(LARGE_BLOCK_LEN
, 'X');
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
));
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
));
65 // when chain_setxattr is used to store value that is
66 // CHAIN_XATTR_MAX_BLOCK_LEN * 2 + 10 bytes long it
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
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
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
80 // see http://marc.info/?l=ceph-devel&m=136027076615853&w=4 for the
84 const string name
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '@');
85 const string
x(LARGE_BLOCK_LEN
, 'X');
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
));
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
));
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"));
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";
132 const string name
= user
+ string(CHAIN_XATTR_MAX_NAME_LEN
- user
.size(), '@');
133 const string
x(LARGE_BLOCK_LEN
, 'X');
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()));
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()));
155 TEST(chain_xattr
, chunk_aligned
) {
156 const char* file
= FILENAME
;
158 int fd
= ::open(file
, O_CREAT
|O_WRONLY
|O_TRUNC
, 0700);
159 const string
user("user.");
162 const string name
= "user.foo";
163 const string name2
= "user.bar";
165 for (int len
= CHAIN_XATTR_MAX_BLOCK_LEN
- 10;
166 len
< CHAIN_XATTR_MAX_BLOCK_LEN
+ 10;
168 cout
<< len
<< std::endl
;
169 const string
x(len
, 'x');
171 ASSERT_EQ(len
, chain_setxattr(file
, name
.c_str(), x
.c_str(), len
));
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
;
177 ASSERT_EQ(len
, chain_getxattr(file
, name
.c_str(), buf
, len
*2));
178 ASSERT_EQ(0, chain_removexattr(file
, name
.c_str()));
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
;
185 ASSERT_EQ(len
, chain_fgetxattr(fd
, name2
.c_str(), buf
, len
*2));
186 ASSERT_EQ(0, chain_fremovexattr(fd
, name2
.c_str()));
189 for (int len
= CHAIN_XATTR_SHORT_BLOCK_LEN
- 10;
190 len
< CHAIN_XATTR_SHORT_BLOCK_LEN
+ 10;
192 cout
<< len
<< std::endl
;
193 const string
x(len
, 'x');
195 ASSERT_EQ(len
, chain_setxattr(file
, name
.c_str(), x
.c_str(), len
));
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
;
201 ASSERT_EQ(len
, chain_getxattr(file
, name
.c_str(), buf
, len
*2));
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));
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));
227 void get_vector_from_xattr(vector
<string
> &xattrs
, char* xattr
, int size
) {
228 char *end
= xattr
+ size
;
229 while (xattr
< end
) {
232 xattrs
.push_back(xattr
);
233 xattr
+= strlen(xattr
) + 1;
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
);
243 if (xattrs1
.size() != xattrs2
.size())
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(),
256 TEST(chain_xattr
, listxattr
) {
257 const char* file
= FILENAME
;
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');
266 int orig_size
= chain_listxattr(file
, NULL
, 0);
267 char *orig_buffer
= NULL
;
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();
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
)));
281 buffer_size
+= orig_size
+ sizeof(char);
282 buffer_size
+= name1
.size() + sizeof(char) + name2
.size() + sizeof(char);
285 char* expected
= (char*)malloc(buffer_size
);
286 ::memset(expected
, '\0', buffer_size
);
288 ::strcpy(expected
, orig_str
.c_str());
289 index
= orig_size
+ 1;
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
));
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));
310 ASSERT_EQ(0, chain_removexattr(file
, name1
.c_str()));
311 ASSERT_EQ(0, chain_removexattr(file
, name2
.c_str()));
319 list
<string
> get_xattrs(int fd
)
323 int len
= sys_flistxattr(fd
, _buf
, sizeof(_buf
));
325 return list
<string
>();
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);
337 list
<string
> get_xattrs(string fn
)
339 int fd
= ::open(fn
.c_str(), O_RDONLY
);
341 return list
<string
>();
342 auto ret
= get_xattrs(fd
);
347 TEST(chain_xattr
, fskip_chain_cleanup_and_ensure_single_attr
)
349 const char *name
= "user.foo";
350 const char *file
= FILENAME
;
352 int fd
= ::open(file
, O_CREAT
|O_RDWR
|O_TRUNC
, 0700);
353 ceph_assert(fd
>= 0);
355 std::size_t existing_xattrs
= get_xattrs(fd
).size();
357 memset(buf
, 0x1F, sizeof(buf
));
358 // set chunked without either
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);
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
)));
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());
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
)));
392 TEST(chain_xattr
, skip_chain_cleanup_and_ensure_single_attr
)
394 const char *name
= "user.foo";
395 const char *file
= FILENAME
;
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();
403 memset(buf
, 0x1F, sizeof(buf
));
404 // set chunked without either
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);
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
)));
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());
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
)));
437 int main(int argc
, char **argv
) {
438 vector
<const char*> args
;
439 argv_to_vec(argc
, (const char **)argv
, args
);
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);
449 const char* file
= FILENAME
;
452 int tmpfd
= ::open(file
, O_CREAT
|O_WRONLY
|O_TRUNC
, 0700);
453 int ret
= ::ceph_os_fsetxattr(tmpfd
, "user.test", &x
, sizeof(x
));
455 ret
= ::ceph_os_fgetxattr(tmpfd
, "user.test", &y
, sizeof(y
));
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
;
461 ::testing::InitGoogleTest(&argc
, argv
);
462 return RUN_ALL_TESTS();
467 // compile-command: "cd ../.. ; make unittest_chain_xattr ; valgrind --tool=memcheck ./unittest_chain_xattr # --gtest_filter=chain_xattr.get_and_set"