]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/cephfs/cephfs-shell
d/control: depend on python3-yaml for ceph-mgr
[ceph.git] / ceph / src / tools / cephfs / cephfs-shell
CommitLineData
11fdf7f2
TL
1#!/usr/bin/python3
2# coding = utf-8
3
4import argparse
5import os
6import os.path
7import sys
11fdf7f2
TL
8import cephfs as libcephfs
9import shutil
10import traceback
11import colorama
11fdf7f2
TL
12import fnmatch
13import math
14import re
15import shlex
9f95a23c
TL
16import stat
17
18from cmd2 import Cmd
19from cmd2 import __version__ as cmd2_version
20from distutils.version import LooseVersion
11fdf7f2 21
81eedcae 22if sys.version_info.major < 3:
9f95a23c 23 raise RuntimeError("cephfs-shell is only compatible with python3")
81eedcae 24
11fdf7f2
TL
25try:
26 from cmd2 import with_argparser
27except ImportError:
28 def with_argparser(argparser):
29 import functools
30
31 def argparser_decorator(func):
32 @functools.wraps(func)
33 def wrapper(thiz, cmdline):
34 if isinstance(cmdline, list):
35 arglist = cmdline
36 else:
37 # do not split if it's already a list
38 arglist = shlex.split(cmdline, posix=False)
39 # in case user quotes the command args
40 arglist = [arg.strip('\'""') for arg in arglist]
41 try:
42 args = argparser.parse_args(arglist)
43 except SystemExit:
9f95a23c 44 shell.exit_code = 1
11fdf7f2
TL
45 # argparse exits at seeing bad arguments
46 return
47 else:
48 return func(thiz, args)
49 argparser.prog = func.__name__[3:]
50 if argparser.description is None and func.__doc__:
51 argparser.description = func.__doc__
52
53 return wrapper
54
55 return argparser_decorator
56
57
9f95a23c
TL
58cephfs = None # holds CephFS Python bindings
59shell = None # holds instance of class CephFSShell
81eedcae 60
9f95a23c
TL
61#########################################################################
62#
63# Following are methods are generically useful through class CephFSShell
64#
65#######################################################################
81eedcae
TL
66
67def poutput(s, end='\n'):
494da23a 68 shell.poutput(s, end=end)
11fdf7f2
TL
69
70
9f95a23c
TL
71def perror(msg, **kwargs):
72 shell.perror(msg, **kwargs)
11fdf7f2
TL
73
74
75def mode_notation(mode):
76 """
77 """
78 permission_bits = {'0': '---',
79 '1': '--x',
80 '2': '-w-',
9f95a23c 81 '3': '-wx',
11fdf7f2
TL
82 '4': 'r--',
83 '5': 'r-x',
84 '6': 'rw-',
85 '7': 'rwx'}
86 mode = str(oct(mode))
87 notation = '-'
88 if mode[2] == '4':
89 notation = 'd'
90 for i in mode[-3:]:
91 notation += permission_bits[i]
92 return notation
93
94
95def get_chunks(file_size):
96 chunk_start = 0
97 chunk_size = 0x20000 # 131072 bytes, default max ssl buffer size
98 while chunk_start + chunk_size < file_size:
99 yield(chunk_start, chunk_size)
100 chunk_start += chunk_size
101 final_chunk_size = file_size - chunk_start
102 yield(chunk_start, final_chunk_size)
103
104
9f95a23c
TL
105def to_bytes(param):
106 # don't convert as follows as it can lead unusable results like coverting
107 # [1, 2, 3, 4] to '[1, 2, 3, 4]' -
108 # str(param).encode('utf-8')
109 if isinstance(param, bytes):
110 return param
111 elif isinstance(param, str):
112 return bytes(param, encoding='utf-8')
113 elif isinstance(param, list):
114 return [i.encode('utf-8') if isinstance(i, str) else to_bytes(i) for \
115 i in param]
116 elif isinstance(param, int) or isinstance(param, float):
117 return str(param).encode('utf-8')
118 elif param is None:
119 return None
11fdf7f2 120
81eedcae
TL
121def ls(path, opts=''):
122 # opts tries to be like /bin/ls opts
123 almost_all = 'A' in opts
124 try:
125 with cephfs.opendir(path) as d:
126 while True:
127 dent = cephfs.readdir(d)
128 if dent is None:
129 return
130 elif almost_all and dent.d_name in (b'.', b'..'):
131 continue
132 yield dent
9f95a23c
TL
133 except libcephfs.ObjectNotFound as e:
134 perror(e)
135 shell.exit_code = e.get_error_code()
11fdf7f2 136
81eedcae 137def glob(path, pattern):
11fdf7f2 138 paths = []
81eedcae 139 parent_dir = os.path.dirname(path)
eafe8130
TL
140 if parent_dir == b'':
141 parent_dir = b'/'
142 if path == b'/' or is_dir_exists(os.path.basename(path), parent_dir):
81eedcae
TL
143 for i in ls(path, opts='A'):
144 if fnmatch.fnmatch(i.d_name, pattern):
145 paths.append(os.path.join(path, i.d_name))
11fdf7f2
TL
146 return paths
147
148
149def locate_file(name, case_sensitive=True):
eafe8130 150 dir_list = sorted(set(dirwalk(cephfs.getcwd())))
11fdf7f2 151 if not case_sensitive:
81eedcae 152 return [dname for dname in dir_list if name.lower() in dname.lower()]
11fdf7f2 153 else:
81eedcae 154 return [dname for dname in dir_list if name in dname]
11fdf7f2
TL
155
156
157def get_all_possible_paths(pattern):
158 complete_pattern = pattern[:]
159 paths = []
160 is_rel_path = not os.path.isabs(pattern)
161 if is_rel_path:
162 dir_ = cephfs.getcwd()
163 else:
eafe8130 164 dir_ = b'/'
11fdf7f2 165 pattern = pattern[1:]
eafe8130 166 patterns = pattern.split(b'/')
11fdf7f2
TL
167 paths.extend(glob(dir_, patterns[0]))
168 patterns.pop(0)
169 for pattern in patterns:
170 for path in paths:
171 paths.extend(glob(path, pattern))
9f95a23c
TL
172 if is_rel_path:
173 complete_pattern = os.path.join(cephfs.getcwd(), complete_pattern)
174 return [path for path in paths if fnmatch.fnmatch(path, complete_pattern)]
11fdf7f2
TL
175
176
177suffixes = ['B', 'K', 'M', 'G', 'T', 'P']
178
179
180def humansize(nbytes):
181 i = 0
9f95a23c 182 while nbytes >= 1024 and i < len(suffixes) - 1:
11fdf7f2
TL
183 nbytes /= 1024.
184 i += 1
185 nbytes = math.ceil(nbytes)
186 f = ('%d' % nbytes).rstrip('.')
187 return '%s%s' % (f, suffixes[i])
188
189
81eedcae 190def print_long(path, is_dir, human_readable):
81eedcae
TL
191 info = cephfs.stat(path)
192 pretty = os.path.basename(path.decode('utf-8'))
11fdf7f2 193 if is_dir:
9f95a23c
TL
194 pretty = colorama.Style.BRIGHT + colorama.Fore.CYAN + pretty + '/'
195 pretty += colorama.Style.RESET_ALL
11fdf7f2 196 if human_readable:
81eedcae 197 poutput('{}\t{:10s} {} {} {} {}'.format(
11fdf7f2
TL
198 mode_notation(info.st_mode),
199 humansize(info.st_size), info.st_uid,
81eedcae 200 info.st_gid, info.st_mtime, pretty, sep='\t'))
11fdf7f2 201 else:
81eedcae 202 poutput('{} {:12d} {} {} {} {}'.format(
11fdf7f2 203 mode_notation(info.st_mode), info.st_size, info.st_uid,
81eedcae 204 info.st_gid, info.st_mtime, pretty, sep='\t'))
11fdf7f2
TL
205
206
207def word_len(word):
208 """
209 Returns the word length, minus any color codes.
210 """
211 if word[0] == '\x1b':
212 return len(word) - 9
213 return len(word)
214
215
eafe8130 216def is_dir_exists(path, dir_=b''):
81eedcae
TL
217 path_to_stat = os.path.join(dir_, path)
218 try:
219 return ((cephfs.stat(path_to_stat).st_mode & 0o0040000) != 0)
220 except libcephfs.Error:
221 return False
11fdf7f2
TL
222
223
eafe8130 224def is_file_exists(path, dir_=b''):
81eedcae
TL
225 try:
226 # if its not a directory, then its a file
227 return ((cephfs.stat(os.path.join(dir_, path)).st_mode & 0o0040000) == 0)
228 except libcephfs.Error:
229 return False
11fdf7f2
TL
230
231
81eedcae 232def print_list(words, termwidth=79):
11fdf7f2
TL
233 if not words:
234 return
eafe8130 235 words = [word.decode('utf-8') if isinstance(word, bytes) else word for word in words]
11fdf7f2
TL
236 width = max([word_len(word) for word in words]) + 2
237 nwords = len(words)
238 ncols = max(1, (termwidth + 1) // (width + 1))
239 nrows = (nwords + ncols - 1) // ncols
240 for row in range(nrows):
241 for i in range(row, nwords, nrows):
242 word = words[i]
81eedcae 243 print_width = width
11fdf7f2 244 if word[0] == '\x1b':
81eedcae
TL
245 print_width = print_width + 10
246
247 poutput('%-*s' % (print_width, words[i]),
248 end='\n' if i + nrows >= nwords else '')
11fdf7f2
TL
249
250
81eedcae 251def copy_from_local(local_path, remote_path):
11fdf7f2 252 stdin = -1
81eedcae
TL
253 file_ = None
254 fd = None
255 convert_to_bytes = False
eafe8130 256 if local_path == b'-':
81eedcae
TL
257 file_ = sys.stdin
258 convert_to_bytes = True
11fdf7f2 259 else:
81eedcae
TL
260 try:
261 file_ = open(local_path, 'rb')
262 except PermissionError:
9f95a23c 263 shell.exit_code = 1
eafe8130 264 perror('error: no permission to read local file {}'.format(
9f95a23c 265 local_path.decode('utf-8')))
81eedcae 266 return
11fdf7f2 267 stdin = 1
81eedcae
TL
268 try:
269 fd = cephfs.open(remote_path, 'w', 0o666)
9f95a23c
TL
270 except libcephfs.Error as e:
271 perror(e)
272 shell.exit_code = 1
11fdf7f2
TL
273 return
274 progress = 0
275 while True:
276 data = file_.read(65536)
81eedcae 277 if not data or len(data) == 0:
11fdf7f2 278 break
81eedcae
TL
279 if convert_to_bytes:
280 data = to_bytes(data)
11fdf7f2
TL
281 wrote = cephfs.write(fd, data, progress)
282 if wrote < 0:
283 break
284 progress += wrote
285 cephfs.close(fd)
286 if stdin > 0:
287 file_.close()
81eedcae 288 poutput('')
11fdf7f2
TL
289
290
81eedcae 291def copy_to_local(remote_path, local_path):
11fdf7f2 292 fd = None
eafe8130 293 if local_path != b'-':
11fdf7f2 294 local_dir = os.path.dirname(local_path)
eafe8130 295 dir_list = remote_path.rsplit(b'/', 1)
11fdf7f2
TL
296 if not os.path.exists(local_dir):
297 os.makedirs(local_dir)
eafe8130 298 if len(dir_list) > 2 and dir_list[1] == b'':
11fdf7f2
TL
299 return
300 fd = open(local_path, 'wb+')
81eedcae 301 file_ = cephfs.open(remote_path, 'r')
11fdf7f2
TL
302 file_size = cephfs.stat(remote_path).st_size
303 if file_size <= 0:
304 return
305 progress = 0
306 for chunk_start, chunk_size in get_chunks(file_size):
307 file_chunk = cephfs.read(file_, chunk_start, chunk_size)
308 progress += len(file_chunk)
309 if fd:
310 fd.write(file_chunk)
311 else:
81eedcae 312 poutput(file_chunk.decode('utf-8'))
11fdf7f2
TL
313 cephfs.close(file_)
314 if fd:
315 fd.close()
316
317
81eedcae 318def dirwalk(path):
11fdf7f2
TL
319 """
320 walk a directory tree, using a generator
321 """
81eedcae
TL
322 path = os.path.normpath(path)
323 for item in ls(path, opts='A'):
eafe8130
TL
324 fullpath = os.path.join(path, item.d_name)
325 src_path = fullpath.rsplit(b'/', 1)[0]
81eedcae
TL
326
327 yield os.path.normpath(fullpath)
328 if is_dir_exists(item.d_name, src_path):
329 for x in dirwalk(fullpath):
330 yield x
11fdf7f2
TL
331
332
9f95a23c
TL
333##################################################################
334#
335# Following methods are implementation for CephFS Shell commands
336#
337#################################################################
338
11fdf7f2
TL
339class CephFSShell(Cmd):
340
341 def __init__(self):
342 super().__init__(use_ipython=False)
343 self.working_dir = cephfs.getcwd().decode('utf-8')
344 self.set_prompt()
345 self.interactive = False
346 self.umask = '2'
347
348 def default(self, line):
9f95a23c 349 perror('Unrecognized command')
11fdf7f2
TL
350
351 def set_prompt(self):
81eedcae
TL
352 self.prompt = ('\033[01;33mCephFS:~' + colorama.Fore.LIGHTCYAN_EX
353 + self.working_dir + colorama.Style.RESET_ALL
354 + '\033[01;33m>>>\033[00m ')
11fdf7f2
TL
355
356 def create_argparser(self, command):
357 try:
358 argparse_args = getattr(self, 'argparse_' + command)
359 except AttributeError:
9f95a23c 360 self.exit_code = 1
11fdf7f2
TL
361 return None
362 doc_lines = getattr(
363 self, 'do_' + command).__doc__.expandtabs().splitlines()
364 if ''in doc_lines:
365 blank_idx = doc_lines.index('')
366 usage = doc_lines[:blank_idx]
367 description = doc_lines[blank_idx + 1:]
368 else:
369 usage = doc_lines
370 description = []
371 parser = argparse.ArgumentParser(
372 prog=command,
373 usage='\n'.join(usage),
374 description='\n'.join(description),
375 formatter_class=argparse.ArgumentDefaultsHelpFormatter
376 )
377 for args, kwargs in argparse_args:
378 parser.add_argument(*args, **kwargs)
379 return parser
380
381 def complete_filenames(self, text, line, begidx, endidx):
382 if not text:
81eedcae
TL
383 completions = [x.d_name.decode('utf-8') + '/' * int(x.is_dir())
384 for x in ls(b".", opts='A')]
11fdf7f2
TL
385 else:
386 if text.count('/') > 0:
81eedcae
TL
387 completions = [text.rsplit('/', 1)[0] + '/'
388 + x.d_name.decode('utf-8') + '/'
389 * int(x.is_dir()) for x in ls('/'
390 + text.rsplit('/', 1)[0], opts='A')
391 if x.d_name.decode('utf-8').startswith(
392 text.rsplit('/', 1)[1])]
11fdf7f2 393 else:
81eedcae
TL
394 completions = [x.d_name.decode('utf-8') + '/'
395 * int(x.is_dir()) for x in ls(b".", opts='A')
396 if x.d_name.decode('utf-8').startswith(text)]
11fdf7f2
TL
397 if len(completions) == 1 and completions[0][-1] == '/':
398 dir_, file_ = completions[0].rsplit('/', 1)
81eedcae
TL
399 completions.extend([dir_ + '/' + x.d_name.decode('utf-8')
400 + '/' * int(x.is_dir()) for x in
401 ls('/' + dir_, opts='A')
402 if x.d_name.decode('utf-8').startswith(file_)])
11fdf7f2
TL
403 return self.delimiter_complete(text, line, begidx, endidx, completions, '/')
404 return completions
405
9f95a23c 406 def onecmd(self, line, **kwargs):
11fdf7f2
TL
407 """
408 Global error catcher
409 """
410 try:
9f95a23c 411 res = Cmd.onecmd(self, line, **kwargs)
11fdf7f2
TL
412 if self.interactive:
413 self.set_prompt()
414 return res
415 except ConnectionError as e:
9f95a23c
TL
416 perror('***\n{}'.format(e))
417 self.exit_code = 3
11fdf7f2 418 except KeyboardInterrupt:
9f95a23c
TL
419 perror('Command aborted')
420 self.exit_code = 130
421 except (libcephfs.Error, Exception) as e:
422 perror(e)
423 if shell.debug:
424 traceback.print_exc(file=sys.stdout)
425
426 if hasattr(e, 'get_error_code'):
427 self.exit_code = e.get_error_code()
428 else:
429 # XXX: Setting it to 3 so that exceptions not stemming from
430 # CephFS Python bindings can be distinguished.
431 self.exit_code = 3
11fdf7f2 432
eafe8130
TL
433 class path_to_bytes(argparse.Action):
434 def __call__(self, parser, namespace, values, option_string=None):
9f95a23c
TL
435 values = to_bytes(values)
436 setattr(namespace, self.dest, values)
437
438 # TODO: move the necessary contents from here to `class path_to_bytes`.
439 class get_list_of_bytes_path(argparse.Action):
440 def __call__(self, parser, namespace, values, option_string=None):
441 values = to_bytes(values)
442
443 if values == b'.':
444 values = cephfs.getcwd()
445 else:
446 for i in values:
447 if i == b'.':
448 values[values.index(i)] = cephfs.getcwd()
449
eafe8130
TL
450 setattr(namespace, self.dest, values)
451
11fdf7f2
TL
452 def complete_mkdir(self, text, line, begidx, endidx):
453 """
454 auto complete of file name.
455 """
456 return self.complete_filenames(text, line, begidx, endidx)
457
81eedcae
TL
458 class ModeAction(argparse.Action):
459 def __init__(self, option_strings, dest, nargs=None, **kwargs):
460 if nargs is not None and nargs != '?':
461 raise ValueError("more than one modes not allowed")
462 super().__init__(option_strings, dest, **kwargs)
463
464 def __call__(self, parser, namespace, values, option_string=None):
465 o_mode = 0
466 res = None
467 try:
468 o_mode = int(values, base=8)
469 except ValueError:
470 res = re.match('((u?g?o?)|(a?))(=)(r?w?x?)', values)
471 if res is None:
472 parser.error("invalid mode: %s\n"
473 "mode must be a numeric octal literal\n"
474 "or ((u?g?o?)|(a?))(=)(r?w?x?)" %
475 values)
476 else:
477 # we are supporting only assignment of mode and not + or -
478 # as is generally available with the chmod command
479 # eg.
480 # >>> res = re.match('((u?g?o?)|(a?))(=)(r?w?x?)', 'go=')
481 # >>> res.groups()
482 # ('go', 'go', None, '=', '')
483 val = res.groups()
484
485 if val[3] != '=':
486 parser.error("need assignment operator between user "
487 "and mode specifiers")
488 if val[4] == '':
489 parser.error("invalid mode: %s\n"
490 "mode must be combination of: r | w | x" %
491 values)
492 users = ''
493 if val[2] is None:
494 users = val[1]
495 else:
496 users = val[2]
497
498 t_mode = 0
499 if users == 'a':
500 users = 'ugo'
501
502 if 'r' in val[4]:
503 t_mode |= 4
504 if 'w' in val[4]:
505 t_mode |= 2
506 if 'x' in val[4]:
507 t_mode |= 1
508
509 if 'u' in users:
510 o_mode |= (t_mode << 6)
511 if 'g' in users:
512 o_mode |= (t_mode << 3)
513 if 'o' in users:
514 o_mode |= t_mode
515
516 if o_mode < 0:
517 parser.error("invalid mode: %s\n"
518 "mode cannot be negative" % values)
519 if o_mode > 0o777:
520 parser.error("invalid mode: %s\n"
521 "mode cannot be greater than octal 0777" % values)
522
523 setattr(namespace, self.dest, str(oct(o_mode)))
524
11fdf7f2
TL
525 mkdir_parser = argparse.ArgumentParser(
526 description='Create the directory(ies), if they do not already exist.')
527 mkdir_parser.add_argument('dirs', type=str,
eafe8130 528 action=path_to_bytes,
11fdf7f2
TL
529 metavar='DIR_NAME',
530 help='Name of new_directory.',
531 nargs='+')
532 mkdir_parser.add_argument('-m', '--mode', type=str,
81eedcae 533 action=ModeAction,
11fdf7f2
TL
534 help='Sets the access mode for the new directory.')
535 mkdir_parser.add_argument('-p', '--parent', action='store_true',
9f95a23c
TL
536 help='Create parent directories as necessary. '
537 'When this option is specified, no error is'
538 'reported if a directory already exists.')
11fdf7f2
TL
539
540 @with_argparser(mkdir_parser)
541 def do_mkdir(self, args):
542 """
543 Create directory.
544 """
81eedcae 545 for path in args.dirs:
11fdf7f2
TL
546 if args.mode:
547 permission = int(args.mode, 8)
548 else:
549 permission = 0o777
550 if args.parent:
551 cephfs.mkdirs(path, permission)
552 else:
81eedcae
TL
553 try:
554 cephfs.mkdir(path, permission)
9f95a23c
TL
555 except libcephfs.Error as e:
556 perror(e)
557 self.exit_code = e.get_error_code()
11fdf7f2
TL
558
559 def complete_put(self, text, line, begidx, endidx):
560 """
561 auto complete of file name.
562 """
563 index_dict = {1: self.path_complete}
564 return self.index_based_complete(text, line, begidx, endidx, index_dict)
565
566 put_parser = argparse.ArgumentParser(
567 description='Copy a file/directory to Ceph File System from Local File System.')
eafe8130 568 put_parser.add_argument('local_path', type=str, action=path_to_bytes,
11fdf7f2 569 help='Path of the file in the local system')
eafe8130 570 put_parser.add_argument('remote_path', type=str, action=path_to_bytes,
81eedcae
TL
571 help='Path of the file in the remote system.',
572 nargs='?', default='.')
11fdf7f2
TL
573 put_parser.add_argument('-f', '--force', action='store_true',
574 help='Overwrites the destination if it already exists.')
575
576 @with_argparser(put_parser)
577 def do_put(self, args):
578 """
9f95a23c 579 Copy a local file/directory to CephFS.
11fdf7f2
TL
580 """
581 root_src_dir = args.local_path
582 root_dst_dir = args.remote_path
eafe8130
TL
583 if args.local_path == b'.' or args.local_path == b'./':
584 root_src_dir = os.getcwdb()
585 elif len(args.local_path.rsplit(b'/', 1)) < 2:
586 root_src_dir = os.path.join(os.getcwdb(), args.local_path)
81eedcae 587 else:
eafe8130
TL
588 p = args.local_path.split(b'/')
589 if p[0] == b'.':
590 root_src_dir = os.getcwdb()
81eedcae
TL
591 p.pop(0)
592 while len(p) > 0:
eafe8130
TL
593 root_src_dir += b'/' + p.pop(0)
594
595 if root_dst_dir == b'.':
596 if args.local_path != b'-':
597 root_dst_dir = root_src_dir.rsplit(b'/', 1)[1]
598 if root_dst_dir == b'':
599 root_dst_dir = root_src_dir.rsplit(b'/', 1)[0]
600 a = root_dst_dir.rsplit(b'/', 1)
81eedcae
TL
601 if len(a) > 1:
602 root_dst_dir = a[1]
603 else:
604 root_dst_dir = a[0]
605 else:
9f95a23c
TL
606 perror('error: no filename specified for destination')
607 # TODO: perhaps, we need a more suitable retval?
608 self.exit_code = 1
81eedcae
TL
609 return
610
eafe8130
TL
611 if root_dst_dir[-1] != b'/':
612 root_dst_dir += b'/'
81eedcae 613
eafe8130 614 if args.local_path == b'-' or os.path.isfile(root_src_dir):
81eedcae
TL
615 if not args.force:
616 if os.path.isfile(root_src_dir):
617 dst_file = root_dst_dir
618 if is_file_exists(dst_file):
9f95a23c
TL
619 perror('{}: file exists! use --force to '
620 'overwrite'.format(dst_file.decode('utf-8')))
621 self.exit_code = 1
81eedcae 622 return
eafe8130
TL
623 if args.local_path == b'-':
624 root_src_dir = b'-'
81eedcae 625 copy_from_local(root_src_dir, root_dst_dir)
11fdf7f2
TL
626 else:
627 for src_dir, dirs, files in os.walk(root_src_dir):
eafe8130
TL
628 if isinstance(src_dir, str):
629 src_dir = to_bytes(src_dir)
11fdf7f2 630 dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
eafe8130 631 dst_dir = re.sub(rb'\/+', b'/', cephfs.getcwd()
81eedcae 632 + dst_dir)
eafe8130 633 if args.force and dst_dir != b'/' and not is_dir_exists(
81eedcae
TL
634 dst_dir[:-1]) and not locate_file(dst_dir):
635 try:
636 cephfs.mkdirs(dst_dir, 0o777)
637 except libcephfs.Error:
638 pass
eafe8130 639 if (not args.force) and dst_dir != b'/' and not is_dir_exists(
81eedcae
TL
640 dst_dir) and not os.path.isfile(root_src_dir):
641 try:
642 cephfs.mkdirs(dst_dir, 0o777)
643 except libcephfs.Error:
9f95a23c 644 # TODO: perhaps, set retval to 1?
81eedcae
TL
645 pass
646
11fdf7f2 647 for dir_ in dirs:
81eedcae
TL
648 dir_name = os.path.join(dst_dir, dir_)
649 if not is_dir_exists(dir_name):
650 try:
651 cephfs.mkdirs(dir_name, 0o777)
652 except libcephfs.Error:
9f95a23c 653 # TODO: perhaps, set retval to 1?
81eedcae
TL
654 pass
655
11fdf7f2
TL
656 for file_ in files:
657 src_file = os.path.join(src_dir, file_)
eafe8130 658 dst_file = re.sub(rb'\/+', b'/', b'/' + dst_dir + b'/' + file_)
11fdf7f2
TL
659 if (not args.force) and is_file_exists(dst_file):
660 return
eafe8130
TL
661 copy_from_local(src_file, os.path.join(cephfs.getcwd(),
662 dst_file))
11fdf7f2
TL
663
664 def complete_get(self, text, line, begidx, endidx):
665 """
666 auto complete of file name.
667 """
668 return self.complete_filenames(text, line, begidx, endidx)
669
670 get_parser = argparse.ArgumentParser(
671 description='Copy a file from Ceph File System from Local Directory.')
eafe8130 672 get_parser.add_argument('remote_path', type=str, action=path_to_bytes,
11fdf7f2 673 help='Path of the file in the remote system')
eafe8130 674 get_parser.add_argument('local_path', type=str, action=path_to_bytes,
81eedcae
TL
675 help='Path of the file in the local system',
676 nargs='?', default='.')
677 get_parser.add_argument('-f', '--force', action='store_true',
678 help='Overwrites the destination if it already exists.')
11fdf7f2
TL
679
680 @with_argparser(get_parser)
681 def do_get(self, args):
682 """
9f95a23c 683 Copy a file/directory from CephFS to given path.
11fdf7f2
TL
684 """
685 root_src_dir = args.remote_path
686 root_dst_dir = args.local_path
eafe8130
TL
687 fname = root_src_dir.rsplit(b'/', 1)
688 if args.local_path == b'.':
689 root_dst_dir = os.getcwdb()
690 if args.remote_path == b'.':
691 root_src_dir = cephfs.getcwd()
692 if args.local_path == b'-':
693 if args.remote_path == b'.' or args.remote_path == b'./':
9f95a23c
TL
694 perror('error: no remote file name specified')
695 self.exit_code = 1
81eedcae 696 return
eafe8130 697 copy_to_local(root_src_dir, b'-')
11fdf7f2 698 elif is_file_exists(args.remote_path):
81eedcae 699 copy_to_local(root_src_dir,
eafe8130
TL
700 root_dst_dir + b'/' + root_src_dir)
701 elif b'/'in root_src_dir and is_file_exists(fname[1], fname[0]):
81eedcae 702 copy_to_local(root_src_dir, root_dst_dir)
11fdf7f2
TL
703 else:
704 files = list(reversed(sorted(dirwalk(root_src_dir))))
705 if len(files) == 0:
81eedcae 706 try:
eafe8130 707 os.makedirs(root_dst_dir + b'/' + root_src_dir)
9f95a23c 708 except OSError as e:
81eedcae
TL
709 if args.force:
710 pass
711 else:
9f95a23c
TL
712 perror('{}: already exists! use --force to overwrite'.format(
713 root_src_dir.decode('utf-8')))
714 self.exit_code = e.get_error_code()
81eedcae
TL
715 return
716
11fdf7f2 717 for file_ in files:
eafe8130 718 dst_dirpath, dst_file = file_.rsplit(b'/', 1)
11fdf7f2
TL
719 if dst_dirpath in files:
720 files.remove(dst_dirpath)
721 dst_path = os.path.join(root_dst_dir, dst_dirpath, dst_file)
722 dst_path = os.path.normpath(dst_path)
11fdf7f2 723 if is_dir_exists(file_):
81eedcae
TL
724 try:
725 os.makedirs(dst_path)
726 except OSError:
727 pass
11fdf7f2 728 else:
81eedcae
TL
729 if not args.force:
730 try:
731 os.stat(dst_path)
9f95a23c
TL
732 perror('{}: file already exists! use --force to '
733 'override'.format(file_.decode('utf-8')))
734 self.exit_code = 1
81eedcae
TL
735 return
736 except OSError:
737 copy_to_local(file_, dst_path)
738 else:
739 copy_to_local(file_, dst_path)
740
11fdf7f2
TL
741 return 0
742
743 def complete_ls(self, text, line, begidx, endidx):
744 """
745 auto complete of file name.
746 """
747 return self.complete_filenames(text, line, begidx, endidx)
748
749 ls_parser = argparse.ArgumentParser(
750 description='Copy a file from Ceph File System from Local Directory.')
751 ls_parser.add_argument('-l', '--long', action='store_true',
752 help='Detailed list of items in the directory.')
753 ls_parser.add_argument('-r', '--reverse', action='store_true',
754 help='Reverse order of listing items in the directory.')
755 ls_parser.add_argument('-H', action='store_true', help='Human Readable')
756 ls_parser.add_argument('-a', '--all', action='store_true',
757 help='Do not Ignore entries starting with .')
758 ls_parser.add_argument('-S', action='store_true', help='Sort by file_size')
eafe8130
TL
759 ls_parser.add_argument('paths', help='Name of Directories',
760 action=path_to_bytes, nargs='*', default=['.'])
11fdf7f2
TL
761
762 @with_argparser(ls_parser)
763 def do_ls(self, args):
764 """
765 List all the files and directories in the current working directory
766 """
81eedcae
TL
767 paths = args.paths
768 for path in paths:
11fdf7f2
TL
769 values = []
770 items = []
eafe8130 771 if path.count(b'*') > 0:
81eedcae 772 all_items = get_all_possible_paths(path)
11fdf7f2
TL
773 if len(all_items) == 0:
774 continue
eafe8130
TL
775 path = all_items[0].rsplit(b'/', 1)[0]
776 if path == b'':
777 path = b'/'
11fdf7f2
TL
778 dirs = []
779 for i in all_items:
81eedcae 780 for item in ls(path):
eafe8130 781 d_name = item.d_name
11fdf7f2
TL
782 if os.path.basename(i) == d_name:
783 if item.is_dir():
81eedcae 784 dirs.append(os.path.join(path, d_name))
11fdf7f2
TL
785 else:
786 items.append(item)
787 if dirs:
81eedcae 788 paths.extend(dirs)
11fdf7f2 789 else:
9f95a23c 790 poutput(path.decode('utf-8'), end=':\n')
11fdf7f2
TL
791 items = sorted(items, key=lambda item: item.d_name)
792 else:
eafe8130 793 if path != b'' and path != cephfs.getcwd() and len(paths) > 1:
9f95a23c 794 poutput(path.decode('utf-8'), end=':\n')
81eedcae 795 items = sorted(ls(path),
11fdf7f2 796 key=lambda item: item.d_name)
81eedcae
TL
797 if not args.all:
798 items = [i for i in items if not i.d_name.startswith(b'.')]
799
11fdf7f2
TL
800 if args.S:
801 items = sorted(items, key=lambda item: cephfs.stat(
eafe8130 802 path + b'/' + item.d_name).st_size)
81eedcae 803
11fdf7f2
TL
804 if args.reverse:
805 items = reversed(items)
806 for item in items:
eafe8130
TL
807 filepath = item.d_name
808 is_dir = item.is_dir()
81eedcae 809
11fdf7f2 810 if args.long and args.H:
9f95a23c 811 print_long(os.path.join(cephfs.getcwd(), path, filepath),
eafe8130 812 is_dir, True)
11fdf7f2 813 elif args.long:
9f95a23c 814 print_long(os.path.join(cephfs.getcwd(), path, filepath),
81eedcae 815 is_dir, False)
11fdf7f2 816 elif is_dir:
eafe8130
TL
817 values.append(colorama.Style.BRIGHT
818 + colorama.Fore.CYAN
819 + filepath.decode('utf-8')
820 + '/'
821 + colorama.Style.RESET_ALL)
11fdf7f2 822 else:
eafe8130 823 values.append(filepath)
11fdf7f2 824 if not args.long:
81eedcae
TL
825 print_list(values, shutil.get_terminal_size().columns)
826 if path != paths[-1]:
9f95a23c 827 poutput('')
11fdf7f2
TL
828
829 def complete_rmdir(self, text, line, begidx, endidx):
830 """
831 auto complete of file name.
832 """
833 return self.complete_filenames(text, line, begidx, endidx)
834
835 rmdir_parser = argparse.ArgumentParser(description='Remove Directory.')
eafe8130
TL
836 rmdir_parser.add_argument('paths', help='Directory Path.', nargs='+',
837 action=path_to_bytes)
11fdf7f2 838 rmdir_parser.add_argument('-p', '--parent', action='store_true',
9f95a23c
TL
839 help='Remove parent directories as necessary. '
840 'When this option is specified, no error '
841 'is reported if a directory has any '
842 'sub-directories, files')
11fdf7f2
TL
843
844 @with_argparser(rmdir_parser)
845 def do_rmdir(self, args):
9f95a23c
TL
846 self.do_rmdir_helper(args)
847
848 def do_rmdir_helper(self, args):
11fdf7f2
TL
849 """
850 Remove a specific Directory
851 """
852 is_pattern = False
81eedcae
TL
853 paths = args.paths
854 for path in paths:
eafe8130 855 if path.count(b'*') > 0:
11fdf7f2 856 is_pattern = True
81eedcae 857 all_items = get_all_possible_paths(path)
11fdf7f2 858 if len(all_items) > 0:
eafe8130
TL
859 path = all_items[0].rsplit(b'/', 1)[0]
860 if path == b'':
861 path = b'/'
11fdf7f2
TL
862 dirs = []
863 for i in all_items:
81eedcae
TL
864 for item in ls(path):
865 d_name = item.d_name
11fdf7f2
TL
866 if os.path.basename(i) == d_name:
867 if item.is_dir():
81eedcae
TL
868 dirs.append(os.path.join(path, d_name))
869 paths.extend(dirs)
11fdf7f2
TL
870 continue
871 else:
872 is_pattern = False
9f95a23c 873
11fdf7f2 874 if args.parent:
9f95a23c
TL
875 path = os.path.join(cephfs.getcwd(), path.rsplit(b'/')[0])
876 files = list(sorted(set(dirwalk(path)), reverse=True))
877 if not files:
878 path = b'.'
eafe8130 879 for filepath in files:
9f95a23c
TL
880 try:
881 cephfs.rmdir(os.path.normpath(filepath))
882 except libcephfs.Error as e:
883 perror(e)
884 path = b'.'
885 break
886 else:
887 path = os.path.normpath(os.path.join(cephfs.getcwd(), path))
eafe8130 888 if not is_pattern and path != os.path.normpath(b''):
81eedcae
TL
889 try:
890 cephfs.rmdir(path)
9f95a23c
TL
891 except libcephfs.Error as e:
892 perror(e)
893 self.exit_code = e.get_error_code()
11fdf7f2
TL
894
895 def complete_rm(self, text, line, begidx, endidx):
896 """
897 auto complete of file name.
898 """
899 return self.complete_filenames(text, line, begidx, endidx)
900
901 rm_parser = argparse.ArgumentParser(description='Remove File.')
eafe8130
TL
902 rm_parser.add_argument('paths', help='File Path.', nargs='+',
903 action=path_to_bytes)
11fdf7f2
TL
904
905 @with_argparser(rm_parser)
906 def do_rm(self, args):
907 """
908 Remove a specific file
909 """
494da23a
TL
910 file_paths = args.paths
911 for path in file_paths:
eafe8130 912 if path.count(b'*') > 0:
494da23a 913 file_paths.extend([i for i in get_all_possible_paths(
81eedcae 914 path) if is_file_exists(i)])
11fdf7f2 915 else:
81eedcae
TL
916 try:
917 cephfs.unlink(path)
9f95a23c
TL
918 except libcephfs.Error as e:
919 # NOTE: perhaps we need a better msg here
920 perror(e)
921 self.exit_code = e.get_error_code()
11fdf7f2
TL
922
923 def complete_mv(self, text, line, begidx, endidx):
924 """
925 auto complete of file name.
926 """
927 return self.complete_filenames(text, line, begidx, endidx)
928
929 mv_parser = argparse.ArgumentParser(description='Move File.')
eafe8130
TL
930 mv_parser.add_argument('src_path', type=str, action=path_to_bytes,
931 help='Source File Path.')
932 mv_parser.add_argument('dest_path', type=str, action=path_to_bytes,
11fdf7f2
TL
933 help='Destination File Path.')
934
935 @with_argparser(mv_parser)
936 def do_mv(self, args):
937 """
938 Rename a file or Move a file from source path to the destination
939 """
9f95a23c 940 cephfs.rename(args.src_path, args.dest_path)
11fdf7f2
TL
941
942 def complete_cd(self, text, line, begidx, endidx):
943 """
944 auto complete of file name.
945 """
946 return self.complete_filenames(text, line, begidx, endidx)
947
948 cd_parser = argparse.ArgumentParser(description='Change working directory')
eafe8130
TL
949 cd_parser.add_argument('path', type=str, help='Name of the directory.',
950 action=path_to_bytes, nargs='?', default='/')
11fdf7f2
TL
951
952 @with_argparser(cd_parser)
953 def do_cd(self, args):
954 """
955 Change working directory
956 """
9f95a23c
TL
957 cephfs.chdir(args.path)
958 self.working_dir = cephfs.getcwd().decode('utf-8')
959 self.set_prompt()
11fdf7f2
TL
960
961 def do_cwd(self, arglist):
962 """
963 Get current working directory.
964 """
9f95a23c 965 poutput(cephfs.getcwd().decode('utf-8'))
11fdf7f2
TL
966
967 def complete_chmod(self, text, line, begidx, endidx):
968 """
969 auto complete of file name.
970 """
971 return self.complete_filenames(text, line, begidx, endidx)
972
973 chmod_parser = argparse.ArgumentParser(description='Create Directory.')
81eedcae 974 chmod_parser.add_argument('mode', type=str, action=ModeAction, help='Mode')
eafe8130
TL
975 chmod_parser.add_argument('paths', type=str, action=path_to_bytes,
976 help='Name of the file', nargs='+')
11fdf7f2
TL
977
978 @with_argparser(chmod_parser)
979 def do_chmod(self, args):
980 """
981 Change permission of a file
982 """
81eedcae
TL
983 for path in args.paths:
984 mode = int(args.mode, base=8)
985 try:
986 cephfs.chmod(path, mode)
9f95a23c
TL
987 except libcephfs.Error as e:
988 perror(e)
989 self.exit_code = e.get_error_code()
11fdf7f2
TL
990
991 def complete_cat(self, text, line, begidx, endidx):
992 """
993 auto complete of file name.
994 """
995 return self.complete_filenames(text, line, begidx, endidx)
996
997 cat_parser = argparse.ArgumentParser(description='')
eafe8130
TL
998 cat_parser.add_argument('paths', help='Name of Files', action=path_to_bytes,
999 nargs='+')
11fdf7f2
TL
1000
1001 @with_argparser(cat_parser)
1002 def do_cat(self, args):
1003 """
1004 Print contents of a file
1005 """
81eedcae
TL
1006 for path in args.paths:
1007 if is_file_exists(path):
eafe8130 1008 copy_to_local(path, b'-')
81eedcae 1009 else:
9f95a23c
TL
1010 perror('{}: no such file'.format(path.decode('utf-8')))
1011 self.exit_code = 1
11fdf7f2
TL
1012
1013 umask_parser = argparse.ArgumentParser(description='Set umask value.')
81eedcae
TL
1014 umask_parser.add_argument('mode', help='Mode', type=str, action=ModeAction,
1015 nargs='?', default='')
11fdf7f2
TL
1016
1017 @with_argparser(umask_parser)
1018 def do_umask(self, args):
1019 """
1020 Set Umask value.
1021 """
1022 if args.mode == '':
9f95a23c 1023 poutput(self.umask.zfill(4))
11fdf7f2
TL
1024 else:
1025 mode = int(args.mode, 8)
1026 self.umask = str(oct(cephfs.umask(mode))[2:])
1027
1028 def complete_write(self, text, line, begidx, endidx):
1029 """
1030 auto complete of file name.
1031 """
1032 return self.complete_filenames(text, line, begidx, endidx)
1033
eafe8130
TL
1034 write_parser = argparse.ArgumentParser(description='Writes data into a file')
1035 write_parser.add_argument('path', type=str, action=path_to_bytes,
1036 help='Name of File')
11fdf7f2
TL
1037
1038 @with_argparser(write_parser)
1039 def do_write(self, args):
1040 """
1041 Write data into a file.
1042 """
1043
eafe8130 1044 copy_from_local(b'-', args.path)
11fdf7f2
TL
1045
1046 def complete_lcd(self, text, line, begidx, endidx):
1047 """
1048 auto complete of file name.
1049 """
1050 index_dict = {1: self.path_complete}
1051 return self.index_based_complete(text, line, begidx, endidx, index_dict)
1052
1053 lcd_parser = argparse.ArgumentParser(description='')
eafe8130 1054 lcd_parser.add_argument('path', type=str, action=path_to_bytes, help='Path')
11fdf7f2
TL
1055
1056 @with_argparser(lcd_parser)
1057 def do_lcd(self, args):
1058 """
1059 Moves into the given local directory
1060 """
81eedcae
TL
1061 try:
1062 os.chdir(os.path.expanduser(args.path))
1063 except OSError as e:
9f95a23c
TL
1064 perror("Cannot change to {}: {}".format(e.filename, e.strerror))
1065 self.exit_code = e.get_error_code()
11fdf7f2
TL
1066
1067 def complete_lls(self, text, line, begidx, endidx):
1068 """
1069 auto complete of file name.
1070 """
1071 index_dict = {1: self.path_complete}
1072 return self.index_based_complete(text, line, begidx, endidx, index_dict)
1073
1074 lls_parser = argparse.ArgumentParser(
1075 description='List files in local system.')
eafe8130
TL
1076 lls_parser.add_argument('paths', help='Paths', action=path_to_bytes,
1077 nargs='*')
11fdf7f2
TL
1078
1079 @with_argparser(lls_parser)
1080 def do_lls(self, args):
1081 """
1082 Lists all files and folders in the current local directory
1083 """
81eedcae 1084 if not args.paths:
eafe8130 1085 print_list(os.listdir(os.getcwdb()))
81eedcae
TL
1086 else:
1087 for path in args.paths:
1088 try:
1089 items = os.listdir(path)
9f95a23c 1090 poutput("{}:".format(path.decode('utf-8')))
81eedcae
TL
1091 print_list(items)
1092 except OSError as e:
9f95a23c
TL
1093 perror("'{}': {}".format(e.filename, e.strerror))
1094 self.exit_code = e.get_error_code()
81eedcae
TL
1095 # Arguments to the with_argpaser decorator function are sticky.
1096 # The items in args.path do not get overwritten in subsequent calls.
1097 # The arguments remain in args.paths after the function exits and we
1098 # neeed to clean it up to ensure the next call works as expected.
1099 args.paths.clear()
11fdf7f2
TL
1100
1101 def do_lpwd(self, arglist):
1102 """
1103 Prints the absolute path of the current local directory
1104 """
9f95a23c
TL
1105 poutput(os.getcwd())
1106
1107 def complete_df(self, text, line, begidx, endidx):
1108 """
1109 auto complete of file name.
1110 """
1111 return self.complete_filenames(text, line, begidx, endidx)
11fdf7f2 1112
9f95a23c
TL
1113 df_parser = argparse.ArgumentParser(description='Show information about\
1114 the amount of available disk space')
1115 df_parser.add_argument('file', help='Name of the file', nargs='*',
1116 default=['.'], action=path_to_bytes)
1117
1118 @with_argparser(df_parser)
11fdf7f2
TL
1119 def do_df(self, arglist):
1120 """
1121 Display the amount of available disk space for file systems
1122 """
9f95a23c
TL
1123 header = True # Set to true for printing header only once
1124 if b'.' == arglist.file[0]:
1125 arglist.file = ls(b'.')
1126
1127 for file in arglist.file:
1128 if isinstance(file, libcephfs.DirEntry):
1129 file = file.d_name
1130 if file == b'.' or file == b'..':
1131 continue
1132 try:
1133 statfs = cephfs.statfs(file)
1134 stat = cephfs.stat(file)
11fdf7f2
TL
1135 block_size = statfs['f_blocks']*statfs['f_bsize'] // 1024
1136 available = block_size - stat.st_size
1137 use = 0
9f95a23c 1138
11fdf7f2
TL
1139 if block_size > 0:
1140 use = (stat.st_size*100 // block_size)
9f95a23c
TL
1141
1142 if header:
1143 header = False
1144 poutput('{:25s}\t{:5s}\t{:15s}{:10s}{}'.format(
1145 "1K-blocks", "Used", "Available", "Use%",
1146 "Stored on"))
1147
1148 poutput('{:d}\t{:18d}\t{:8d}\t{:10s} {}'.format(block_size,
1149 stat.st_size, available, str(int(use)) + '%',
1150 file.decode('utf-8')))
1151 except libcephfs.OSError as e:
1152 perror("could not statfs {}: {}".format(file.decode('utf-8'),
1153 e.strerror))
1154 self.exit_code = e.get_error_code()
11fdf7f2
TL
1155
1156 locate_parser = argparse.ArgumentParser(
1157 description='Find file within file system')
eafe8130
TL
1158 locate_parser.add_argument('name', help='name', type=str,
1159 action=path_to_bytes)
81eedcae
TL
1160 locate_parser.add_argument('-c', '--count', action='store_true',
1161 help='Count list of items located.')
11fdf7f2
TL
1162 locate_parser.add_argument(
1163 '-i', '--ignorecase', action='store_true', help='Ignore case')
1164
1165 @with_argparser(locate_parser)
1166 def do_locate(self, args):
1167 """
1168 Find a file within the File System
1169 """
eafe8130
TL
1170 if args.name.count(b'*') == 1:
1171 if args.name[0] == b'*':
1172 args.name += b'/'
11fdf7f2 1173 elif args.name[-1] == '*':
eafe8130
TL
1174 args.name = b'/' + args.name
1175 args.name = args.name.replace(b'*', b'')
11fdf7f2
TL
1176 if args.ignorecase:
1177 locations = locate_file(args.name, False)
1178 else:
1179 locations = locate_file(args.name)
1180 if args.count:
9f95a23c 1181 poutput(len(locations))
11fdf7f2 1182 else:
9f95a23c 1183 poutput((b'\n'.join(locations)).decode('utf-8'))
11fdf7f2
TL
1184
1185 def complete_du(self, text, line, begidx, endidx):
1186 """
1187 auto complete of file name.
1188 """
1189 return self.complete_filenames(text, line, begidx, endidx)
1190
1191 du_parser = argparse.ArgumentParser(
1192 description='Disk Usage of a Directory')
9f95a23c
TL
1193 du_parser.add_argument('paths', type=str, action=get_list_of_bytes_path,
1194 help='Name of the directory.', nargs='*',
1195 default=[b'.'])
11fdf7f2
TL
1196 du_parser.add_argument('-r', action='store_true',
1197 help='Recursive Disk usage of all directories.')
1198
1199 @with_argparser(du_parser)
1200 def do_du(self, args):
1201 """
9f95a23c 1202 Print disk usage of a given path(s).
11fdf7f2 1203 """
9f95a23c
TL
1204 def print_disk_usage(files):
1205 if isinstance(files, bytes):
1206 files = (files, )
1207
1208 for f in files:
1209 try:
1210 st = cephfs.lstat(f)
1211
1212 if stat.S_ISDIR(st.st_mode):
1213 dusage = int(cephfs.getxattr(f,
1214 'ceph.dir.rbytes').decode('utf-8'))
1215 else:
1216 dusage = st.st_size
1217
1218 # print path in local context
1219 f = os.path.normpath(f)
1220 if f[0] is ord('/'):
1221 f = b'.' + f
1222 poutput('{:10s} {}'.format(humansize(dusage),
1223 f.decode('utf-8')))
1224 except libcephfs.Error as e:
1225 perror(e)
1226 self.exit_code = e.get_error_code()
1227 continue
1228
1229 for path in args.paths:
11fdf7f2 1230 if args.r:
9f95a23c 1231 print_disk_usage(sorted(set(dirwalk(path)).union({path})))
11fdf7f2 1232 else:
9f95a23c 1233 print_disk_usage(path)
81eedcae
TL
1234
1235 quota_parser = argparse.ArgumentParser(
1236 description='Quota management for a Directory')
1237 quota_parser.add_argument('op', choices=['get', 'set'],
1238 help='Quota operation type.')
eafe8130
TL
1239 quota_parser.add_argument('path', type=str, action=path_to_bytes,
1240 help='Name of the directory.')
81eedcae
TL
1241 quota_parser.add_argument('--max_bytes', type=int, default=-1, nargs='?',
1242 help='Max cumulative size of the data under '
1243 'this directory.')
1244 quota_parser.add_argument('--max_files', type=int, default=-1, nargs='?',
1245 help='Total number of files under this '
1246 'directory tree.')
1247
1248 @with_argparser(quota_parser)
1249 def do_quota(self, args):
1250 """
1251 Quota management.
1252 """
1253 if not is_dir_exists(args.path):
9f95a23c
TL
1254 perror('error: no such directory {}'.format(args.path.decode('utf-8')))
1255 self.exit_code = 1
81eedcae
TL
1256 return
1257
1258 if args.op == 'set':
1259 if (args.max_bytes == -1) and (args.max_files == -1):
9f95a23c
TL
1260 perror('please specify either --max_bytes or --max_files or '
1261 'both')
1262 self.exit_code = 1
81eedcae
TL
1263 return
1264
1265 if args.max_bytes >= 0:
1266 max_bytes = to_bytes(str(args.max_bytes))
1267 try:
1268 cephfs.setxattr(args.path, 'ceph.quota.max_bytes',
9f95a23c
TL
1269 max_bytes, os.XATTR_CREATE)
1270 poutput('max_bytes set to %d' % args.max_bytes)
1271 except libcephfs.Error as e:
81eedcae 1272 cephfs.setxattr(args.path, 'ceph.quota.max_bytes',
9f95a23c
TL
1273 max_bytes, os.XATTR_REPLACE)
1274 perror('max_bytes reset to %d' % args.max_bytes)
1275 self.exit_code = e.get_error_code()
81eedcae
TL
1276
1277 if args.max_files >= 0:
1278 max_files = to_bytes(str(args.max_files))
1279 try:
1280 cephfs.setxattr(args.path, 'ceph.quota.max_files',
9f95a23c
TL
1281 max_files, os.XATTR_CREATE)
1282 poutput('max_files set to %d' % args.max_files)
1283 except libcephfs.Error as e:
81eedcae 1284 cephfs.setxattr(args.path, 'ceph.quota.max_files',
9f95a23c
TL
1285 max_files, os.XATTR_REPLACE)
1286 perror('max_files reset to %d' % args.max_files)
1287 self.exit_code = e.get_error_code()
81eedcae
TL
1288 elif args.op == 'get':
1289 max_bytes = '0'
1290 max_files = '0'
1291 try:
9f95a23c
TL
1292 max_bytes = cephfs.getxattr(args.path, 'ceph.quota.max_bytes')
1293 poutput('max_bytes: {}'.format(max_bytes.decode('utf-8')))
1294 except libcephfs.Error as e:
1295 perror('max_bytes is not set')
1296 self.exit_code = e.get_error_code()
81eedcae
TL
1297
1298 try:
9f95a23c
TL
1299 max_files = cephfs.getxattr(args.path, 'ceph.quota.max_files')
1300 poutput('max_files: {}'.format(max_files.decode('utf-8')))
1301 except libcephfs.Error as e:
1302 perror('max_files is not set')
1303 self.exit_code = e.get_error_code()
1304
1305 snap_parser = argparse.ArgumentParser(description='Snapshot Management')
1306 snap_parser.add_argument('op', type=str,
1307 help='Snapshot operation: create or delete')
1308 snap_parser.add_argument('name', type=str, action=path_to_bytes,
1309 help='Name of snapshot')
1310 snap_parser.add_argument('dir', type=str, action=path_to_bytes,
1311 help='Directory for which snapshot '
1312 'needs to be created or deleted')
1313
1314 @with_argparser(snap_parser)
1315 def do_snap(self, args):
1316 """
1317 Snapshot management for the volume
1318 """
1319 # setting self.colors to None turns off colorizing and
1320 # perror emits plain text
1321 self.colors = None
1322
1323 snapdir = '.snap'
1324 conf_snapdir = cephfs.conf_get('client_snapdir')
1325 if conf_snapdir is not None:
1326 snapdir = conf_snapdir
1327 snapdir = to_bytes(snapdir)
1328 if args.op == 'create':
1329 try:
1330 if is_dir_exists(args.dir):
1331 cephfs.mkdir(os.path.join(args.dir, snapdir, args.name), 0o755)
1332 else:
1333 self.perror("'{}': no such directory".format(
1334 args.dir.decode('utf-8')))
1335 self.exit_code = 1
1336 except libcephfs.Error as e:
1337 self.perror("snapshot '{}' already exists".format(
1338 args.name.decode('utf-8')))
1339 self.exit_code = e.get_error_code()
1340 elif args.op == 'delete':
1341 snap_dir = os.path.join(args.dir, snapdir, args.name)
1342 try:
1343 if is_dir_exists(snap_dir):
1344 newargs = argparse.Namespace(paths=[snap_dir], parent=False)
1345 self.do_rmdir_helper(newargs)
1346 else:
1347 self.perror("'{}': no such snapshot".format(
1348 args.name.decode('utf-8')))
1349 self.exit_code = 1
1350 except libcephfs.Error as e:
1351 self.perror("error while deleting '{}'".format(
1352 snap_dir.decode('utf-8')))
1353 self.exit_code = e.get_error_code()
1354 else:
1355 self.perror("snapshot can only be created or deleted; check - "
1356 "help snap")
1357 self.exit_code = e.get_error_code()
11fdf7f2
TL
1358
1359 def do_help(self, line):
1360 """
1361 Get details about a command.
1362 Usage: help <cmd> - for a specific command
1363 help all - for all the commands
1364 """
1365 if line == 'all':
1366 for k in dir(self):
1367 if k.startswith('do_'):
9f95a23c 1368 poutput('-' * 80)
11fdf7f2
TL
1369 super().do_help(k[3:])
1370 return
1371 parser = self.create_argparser(line)
1372 if parser:
1373 parser.print_help()
1374 else:
1375 super().do_help(line)
1376
81eedcae
TL
1377 def complete_stat(self, text, line, begidx, endidx):
1378 """
1379 auto complete of file name.
1380 """
1381 return self.complete_filenames(text, line, begidx, endidx)
1382
1383 stat_parser = argparse.ArgumentParser(
9f95a23c 1384 description='Display file or file system status')
81eedcae 1385 stat_parser.add_argument('paths', type=str, help='file paths',
eafe8130 1386 action=path_to_bytes, nargs='+')
81eedcae
TL
1387
1388 @with_argparser(stat_parser)
1389 def do_stat(self, args):
1390 """
1391 Display file or file system status
1392 """
1393 for path in args.paths:
1394 try:
1395 stat = cephfs.stat(path)
1396 atime = stat.st_atime.isoformat(' ')
1397 mtime = stat.st_mtime.isoformat(' ')
1398 ctime = stat.st_mtime.isoformat(' ')
1399
9f95a23c
TL
1400 poutput("File: {}\nSize: {:d}\nBlocks: {:d}\nIO Block: {:d}\n"
1401 "Device: {:d}\tInode: {:d}\tLinks: {:d}\nPermission: "
1402 "{:o}/{}\tUid: {:d}\tGid: {:d}\n\Access: {}\nModify: "
1403 "{}\nChange: {}".format(path.decode('utf-8'),
1404 stat.st_size, stat.st_blocks, stat.st_blksize,
1405 stat.st_dev, stat.st_ino, stat.st_nlink, stat.st_mode,
1406 mode_notation(stat.st_mode), stat.st_uid, stat.st_gid,
1407 atime, mtime, ctime))
1408 except libcephfs.Error as e:
1409 perror(e)
1410 self.exit_code = e.get_error_code()
1411
1412 setxattr_parser = argparse.ArgumentParser(
1413 description='Set extended attribute for a file')
1414 setxattr_parser.add_argument('path', type=str, action=path_to_bytes, help='Name of the file')
1415 setxattr_parser.add_argument('name', type=str, help='Extended attribute name')
1416 setxattr_parser.add_argument('value', type=str, help='Extended attribute value')
1417
1418 @with_argparser(setxattr_parser)
1419 def do_setxattr(self, args):
1420 """
1421 Set extended attribute for a file
1422 """
1423 val_bytes = to_bytes(args.value)
1424 name_bytes = to_bytes(args.name)
1425 try:
1426 cephfs.setxattr(args.path, name_bytes, val_bytes, os.XATTR_CREATE)
1427 poutput('{} is successfully set to {}'.format(args.name, args.value))
1428 except libcephfs.ObjectExists:
1429 cephfs.setxattr(args.path, name_bytes, val_bytes, os.XATTR_REPLACE)
1430 poutput('{} is successfully reset to {}'.format(args.name, args.value))
1431 except libcephfs.Error as e:
1432 perror(e)
1433 self.exit_code = e.get_error_code()
1434
1435 getxattr_parser = argparse.ArgumentParser(
1436 description='Get extended attribute set for a file')
1437 getxattr_parser.add_argument('path', type=str, action=path_to_bytes,
1438 help='Name of the file')
1439 getxattr_parser.add_argument('name', type=str, help='Extended attribute name')
1440
1441 @with_argparser(getxattr_parser)
1442 def do_getxattr(self, args):
1443 """
1444 Get extended attribute for a file
1445 """
1446 try:
1447 poutput('{}'.format(cephfs.getxattr(args.path,
1448 to_bytes(args.name)).decode('utf-8')))
1449 except libcephfs.Error as e:
1450 perror(e)
1451 self.exit_code = e.get_error_code()
1452
1453 listxattr_parser = argparse.ArgumentParser(
1454 description='List extended attributes set for a file')
1455 listxattr_parser.add_argument('path', type=str, action=path_to_bytes,
1456 help='Name of the file')
1457
1458 @with_argparser(listxattr_parser)
1459 def do_listxattr(self, args):
1460 """
1461 List extended attributes for a file
1462 """
1463 try:
1464 size, xattr_list = cephfs.listxattr(args.path)
1465 if size > 0:
1466 poutput('{}'.format(xattr_list.replace(b'\x00', b' ').decode('utf-8')))
1467 else:
1468 poutput('No extended attribute is set')
1469 except libcephfs.Error as e:
1470 perror(e)
1471 self.exit_code = e.get_error_code()
1472
1473
1474#######################################################
1475#
1476# Following are methods that get cephfs-shell started.
1477#
1478#####################################################
1479
1480def setup_cephfs(config_file):
1481 """
1482 Mounting a cephfs
1483 """
1484 global cephfs
1485 try:
1486 cephfs = libcephfs.LibCephFS(conffile=config_file)
1487 cephfs.mount()
1488 except libcephfs.ObjectNotFound as e:
1489 print('couldn\'t find ceph configuration not found')
1490 sys.exit(1)
1491 except libcephfs.Error as e:
1492 print(e)
1493 sys.exit(1)
1494
1495def get_bool_vals_for_boolopts(val):
1496 if not isinstance(val, str):
1497 return val
1498
1499 val = val.replace('\n', '')
1500 if val.lower() in ['true', 'yes']:
1501 return True
1502 elif val.lower() in ['false', 'no']:
1503 return False
1504 else:
1505 return val
81eedcae 1506
9f95a23c
TL
1507def read_ceph_conf(shell, config_file):
1508 try:
1509 shell.debug = get_bool_vals_for_boolopts(cephfs.\
1510 conf_get('debug_shell'))
1511 shell.allow_ansi = get_bool_vals_for_boolopts(cephfs.\
1512 conf_get('allow_ansi'))
1513 shell.echo = get_bool_vals_for_boolopts(cephfs.conf_get('echo'))
1514 shell.continuation_prompt = get_bool_vals_for_boolopts(cephfs.\
1515 conf_get('continuation_prompt'))
1516 shell.colors = get_bool_vals_for_boolopts(cephfs.conf_get('colors'))
1517 shell.editor = get_bool_vals_for_boolopts(cephfs.conf_get('editor'))
1518 shell.feedback_to_output = get_bool_vals_for_boolopts(cephfs.\
1519 conf_get('feedback_to_output'))
1520 shell.max_completion_items = get_bool_vals_for_boolopts(cephfs.\
1521 conf_get('max_completion_items'))
1522 shell.prompt = get_bool_vals_for_boolopts(cephfs.conf_get('prompt'))
1523 shell.quiet = get_bool_vals_for_boolopts(cephfs.conf_get('quiet'))
1524 shell.timing = get_bool_vals_for_boolopts(cephfs.conf_get('timing'))
1525 except (OSError, libcephfs.Error) as e:
1526 perror(e)
1527 shell.exit_code = e.get_error_code()
11fdf7f2
TL
1528
1529if __name__ == '__main__':
1530 config_file = ''
9f95a23c 1531
11fdf7f2 1532 exe = sys.argv[0]
9f95a23c 1533
11fdf7f2 1534 main_parser = argparse.ArgumentParser(description='')
81eedcae
TL
1535 main_parser.add_argument('-c', '--config', action='store',
1536 help='Configuration file_path', type=str)
11fdf7f2
TL
1537 main_parser.add_argument(
1538 '-b', '--batch', action='store', help='Batch File path.', type=str)
1539 main_parser.add_argument('-t', '--test', action='store',
81eedcae
TL
1540 help='Test against transcript(s) in FILE',
1541 nargs='+')
11fdf7f2
TL
1542 main_parser.add_argument('commands', nargs='*',
1543 help='comma delimited commands', default=[])
1544 args = main_parser.parse_args()
1545 if args.config:
1546 config_file = args.config
1547 if args.batch:
9f95a23c
TL
1548 if LooseVersion(cmd2_version) <= LooseVersion("0.9.13"):
1549 args.commands = ['load ' + args.batch, ',quit']
1550 else:
1551 args.commands = ['run_script ' + args.batch, ',quit']
11fdf7f2 1552 if args.test:
9f95a23c
TL
1553 args.commands.extend(['-t,'] + [arg + ',' for arg in args.test])
1554
11fdf7f2
TL
1555 sys.argv.clear()
1556 sys.argv.append(exe)
1557 sys.argv.extend([i.strip() for i in ' '.join(args.commands).split(',')])
9f95a23c 1558
11fdf7f2 1559 setup_cephfs(config_file)
81eedcae 1560 shell = CephFSShell()
9f95a23c
TL
1561 shell.exit_code = 0
1562 read_ceph_conf(shell, config_file)
1563 shell.exit_code = shell.cmdloop()
1564
1565 sys.exit(shell.exit_code)