--- /dev/null
+/* Display
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <iostream>
+#include <fmt/chrono.h>
+#include <netdb.h>
+//#include <arpa/inet.h>
+#include "kafs.H"
+#include "vlservice.H"
+#include "afs_xg.H"
+#include "display.H"
+
+using rxrpc::ref;
+
+std::string kafs::sprint_time(const Time &time)
+{
+ return fmt::format("{:%a %b %d %H:%M:%S %Y}", fmt::localtime(time.seconds()));
+}
+
+/**
+ * kafs::sprint_time_or_never - Print a time in standard format
+ * @time: The time to display.
+ *
+ * Print a time in a standard format for AFS tools to a string. If @time is 0,
+ * then "Never" will be printed instead.
+ */
+std::string kafs::sprint_time_or_never(const Time &time)
+{
+ if (time.seconds() == 0)
+ return "Never";
+ else
+ return kafs::sprint_time(time);
+}
+
+/**
+ * kafs::print_time - Print a time in standard format
+ * @time: The time to display.
+ *
+ * Print a time in a standard format for AFS tools to stdout.
+ */
+void kafs::print_time(const Time &time)
+{
+ time_t t = time.seconds();
+ struct tm tm;
+ char buf[128];
+
+ localtime_r(&t, &tm);
+ strftime(buf, 128, "%a %b %d %H:%M:%S %Y", &tm);
+ std::cout << buf;
+}
+
+/**
+ * kafs::print_time_or_never - Print a time in standard format
+ * @time: The time to display.
+ *
+ * Print a time in a standard format for AFS tools to stdout. If @time is 0,
+ * then "Never" will be printed instead.
+ */
+void kafs::print_time_or_never(const Time &time)
+{
+ if (time.seconds() == 0)
+ std::cout << "Never";
+ else
+ kafs::print_time(time);
+}
+
+/**
+ * kafs::sprint_address - Convert/resolve an address into text
+ * @ctx: The cell and authentication context
+ * @addr: The address to convert
+ *
+ * Convert an address into a textual representation, possibly resolving it to a
+ * hostname via the DNS or other name resolution service. The conversion is
+ * written into the buffer and truncated to the length given.
+ */
+std::string kafs::sprint_address(Context *ctx,
+ const struct sockaddr_rxrpc &addr)
+{
+ unsigned short port;
+ char buf[1024], service[64], *b;
+ size_t len;
+ int hint, ni;
+
+ hint = NI_DGRAM | NI_NUMERICSERV;
+
+ if (!ctx->no_resolve) {
+ hint |= NI_NAMEREQD;
+ ni = getnameinfo((struct sockaddr *)&addr.transport,
+ addr.transport_len,
+ buf, sizeof(buf), service, sizeof(service), hint);
+ if (ni == EAI_MEMORY)
+ throw rxrpc::nomem();
+ if (ni == 0)
+ return fmt::format("{}:{}", buf, service);
+ }
+
+ hint &= ~NI_NAMEREQD;
+ hint |= NI_NUMERICHOST;
+ b = buf + 1;
+ ni = getnameinfo((struct sockaddr *)&addr.transport,
+ addr.transport_len,
+ b, sizeof(buf) - 2, service, sizeof(service), hint);
+ if (ni == EAI_MEMORY)
+ throw rxrpc::nomem();
+
+ if (strchr(b, ':')) {
+ len = strlen(b);
+ b[-1] = '[';
+ b[len] = ']';
+ b[len + 1] = 0;
+ b--;
+ }
+
+ if (ni == 0)
+ return fmt::format("{}:{}", b, service);
+
+ buf[0] = 0;
+ switch (addr.transport.sin.sin_family) {
+ case AF_INET:
+ inet_ntop(AF_INET, &addr.transport.sin.sin_addr, buf, sizeof(buf));
+ port = ntohs(addr.transport.sin.sin_port);
+ break;
+ case AF_INET6:
+ buf[0] = '[';
+ inet_ntop(AF_INET6, &addr.transport.sin6.sin6_addr, buf + 1, sizeof(buf) - 2);
+ len = strlen(buf);
+ buf[len] = ']';
+ buf[len + 1] = 0;
+ port = ntohs(addr.transport.sin6.sin6_port);
+ break;
+ default:
+ return fmt::format("<af-{}>", addr.transport.sin.sin_family);
+ }
+
+ if (port == afs::FS_PORT)
+ return std::string(buf);
+
+ return fmt::format("{}+{:d}", b, port);
+}
+
+/*
+ * kafs::sprint_address - Convert a numeric ID into a partition name string.
+ * @ctx: The cell and authentication context
+ * @partition: The partition specified to convert
+ *
+ * Render the numeric partition ID to the standard string representation and
+ * write it into the buffer.
+ */
+std::string kafs::sprint_partition(const Partition_spec &partition)
+{
+ unsigned int n = partition.id;
+
+ if (n < 26) {
+ return fmt::format("/vicep{:c}", (char)(n + 97));
+ } else if (n <= 255) {
+ n -= 26;
+ return fmt::format("/vicep{:c}{:c}", (char)(n / 26 + 97), (char)(n % 26 + 97));
+ } else {
+ return fmt::format("/vicep?{:d}", n);
+ }
+}
--- /dev/null
+/* Volume information display.
+ *
+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <iostream>
+#include <fmt/format.h>
+#include "kafs.H"
+#include "vlservice.H"
+#include "afs_xg.H"
+#include "display.H"
+
+using rxrpc::ref;
+
+/*
+ * Convert a volume site into a textual representation, possibly resolving it
+ * to a DNS hostname.
+ */
+std::string kafs::sprint_site(Context *ctx, Vldb_site &vldb_site)
+{
+ FS_site *site = vldb_site.fs_site;
+
+ if (!site)
+ return "<no-server>";
+
+ std::vector<sockaddr_rxrpc> &addrs = site->vs_addrs;
+
+ if (!addrs.size()) {
+ if (vldb_site.has_uuid) {
+ char buf[48];
+ uuid_unparse(vldb_site.uuid.uuid, buf);
+ return std::string(buf);
+ }
+ return "<no-addrs>";
+ }
+
+ return sprint_address(ctx, addrs[0]);
+}
+
+/*
+ * Display the VLDB site list, looking something like:
+ *
+ * number of sites -> 2
+ * server fserver.abc.com partition /vicepa RW Site
+ * server fserver.abc.com partition /vicepa RO Site
+ */
+void kafs::display_vldb_site_list(Context *ctx,
+ Vldb_entry *vldb,
+ const char *indent)
+{
+ const char *ptype;
+ unsigned int i;
+
+ std::cout << indent << "number of sites -> " << vldb->sites.size() << "\n";
+ for (i = 0; i < vldb->sites.size(); i++) {
+ Vldb_site &site = vldb->sites[i];
+ unsigned int flags = site.flags;
+
+ if (flags & afs::VLSF_ROVOL)
+ ptype = "RO";
+ else if (flags & afs::VLSF_RWVOL)
+ ptype = "RW";
+ else
+ ptype = "Back";
+
+ std::cout << indent
+ << " server " << sprint_site(ctx, site)
+ << " partition " << sprint_partition(site.partition)
+ << " " << ptype << " Site\n";
+ }
+
+ if (vldb->flags & (afs::VLOP_MOVE |
+ afs::VLOP_RELEASE |
+ afs::VLOP_BACKUP |
+ afs::VLOP_DELETE |
+ afs::VLOP_DUMP)
+ ) {
+ std::cout << indent << "Volume is currently LOCKED\n";
+ if (vldb->flags & afs::VLOP_MOVE)
+ std::cout << indent << "Volume is locked for a move operation\n";
+ if (vldb->flags & afs::VLOP_RELEASE)
+ std::cout << indent << "Volume is locked for a release operation\n";
+ if (vldb->flags & afs::VLOP_BACKUP)
+ std::cout << indent << "Volume is locked for a backup operation\n";
+ if (vldb->flags & afs::VLOP_DELETE)
+ std::cout << indent << "Volume is locked for a delete/misc operation\n";
+ if (vldb->flags & afs::VLOP_DUMP)
+ std::cout << indent << "Volume is locked for a dump/restore operation\n";
+ }
+}