From 9b44cf70a95b2ee97a17697dc97fbe462ad1f5b9 Mon Sep 17 00:00:00 2001 From: Gian-Carlo DeFazio Date: Tue, 11 Feb 2020 13:08:18 -0800 Subject: [PATCH] LU-13224 utils: expose llapi_param* functions Added an interface to find files in sysfs,procfs,etc., and read them into a file or buffer. The interface is meant to return the same results as the "lctl list_param" and "lctl get_param" commands, but doesn't require starting an lctl process. The interface is being used by lctl for parts of its "list_param" and "get_param" commands. The output of lctl get_param is altered slightly. Previously, for some multi-line files lctl would print the param name and first line of the file on the same line such as: param=line 1 line 2 line 3 Now multi-line files consistently put a newline after the param name and before the content such as: param= line 1 line 2 line 3 Added debug output to failing test sanity 300d. Signed-off-by: Gian-Carlo DeFazio Change-Id: I2726b27b0042d58c97284f8348970deb6efc43d1 Reviewed-on: https://review.whamcloud.com/37545 Tested-by: jenkins Reviewed-by: Andreas Dilger Reviewed-by: Olaf Faaland-LLNL Tested-by: Maloo Reviewed-by: James Simmons Reviewed-by: Oleg Drokin --- lustre/doc/Makefile.am | 2 + lustre/doc/llapi_param_get_paths.3 | 31 +++++ lustre/doc/llapi_param_get_value.3 | 103 ++++++++++++++++ lustre/include/lustre/lustreapi.h | 5 + lustre/tests/sanity.sh | 10 ++ lustre/utils/liblustreapi_param.c | 242 +++++++++++++++++++++++++++++++++++++ lustre/utils/lustre_cfg.c | 83 ++++--------- 7 files changed, 414 insertions(+), 62 deletions(-) create mode 100644 lustre/doc/llapi_param_get_paths.3 create mode 100644 lustre/doc/llapi_param_get_value.3 diff --git a/lustre/doc/Makefile.am b/lustre/doc/Makefile.am index 03a83a6..54819f0 100644 --- a/lustre/doc/Makefile.am +++ b/lustre/doc/Makefile.am @@ -142,6 +142,8 @@ MANFILES = \ llapi_layout_stripe_count_set.3 \ llapi_layout_stripe_size_get.3 \ llapi_layout_stripe_size_set.3 \ + llapi_param_get_paths.3 \ + llapi_param_get_value.3 \ llapi_path2fid.3 \ llapi_path2parent.3 \ llapi_pcc_attach.3 \ diff --git a/lustre/doc/llapi_param_get_paths.3 b/lustre/doc/llapi_param_get_paths.3 new file mode 100644 index 0000000..852cc7e --- /dev/null +++ b/lustre/doc/llapi_param_get_paths.3 @@ -0,0 +1,31 @@ +.TH llapi_param_get_paths 3 "2020 Feb 24" "Lustre User API" +.SH NAME +llapi_param_get_paths \- get a list of Lustre parameter file paths +that match the given pattern +.SH SYNOPSIS +.B #include +.nf +.BI "int llapi_param_get_paths(const char" " *pattern, " "glob_t" \ +" *paths" ")" +.SH DESCRIPTION +The +.B llapi_param_get_paths(\|) +function searches the locations in the filesystem that expose +Lustre parameters. All file names that match +.I pattern +and are in +the correct locations in the file system are returned. + +.SH RETURN VALUES +.TP +.B 0 +on success with the parameter paths stored in +.IR paths . +.TP +-ve +error code on failure and sets errno appropriately. + +.SH SEE ALSO +.BR llapi_param_get_value (3), +.BR lustreapi (7), +.BR lctl-get_param (8) diff --git a/lustre/doc/llapi_param_get_value.3 b/lustre/doc/llapi_param_get_value.3 new file mode 100644 index 0000000..9392164 --- /dev/null +++ b/lustre/doc/llapi_param_get_value.3 @@ -0,0 +1,103 @@ +.TH llapi_param_get_value 3 "2020 Feb 24" "Lustre User API" +.SH NAME +llapi_param_get_value \- read parameter files for Lustre to a buffer +.SH SYNOPSIS +.nf +.B #include +.sp +.BI "int llapi_param_get_value(const char " "*path" ", char " "**buf" \ +", size_t " "*buflen" ");" +.SH DESCRIPTION +.LP +The +.B llapi_param_get_value() +function reads the contents of files in procfs, sysfs, etc. into a buffer. +If the buffer is preallocated, it will have a fixed size. +If the buffer is not preallocated, it will be automatically sized to fit +the contents of the file. There is also an option to query the size of a file. + +The behavior of +.B llapi_param_get_value() +depends on the value of +.IR buf . +If +.I buf +is NULL, then the contents of +.I path +will not be saved into +.IR *buf , +but the number of bytes needed to hold the file will be stored in +.IR buflen . +This size will include the space needed for the NUL terminating byte. +However, the size of the file can change because it is dynamically generated. +This means that it's possible for the file size to increase which will cause +the value stored in +.I buflen +to still not be large enough when used in a subsequent call. +.sp +If +.I buf +is not NULL but +.I *buf +is NULL, then +.I *buf +will point to an automatically allocated buffer that will hold the contents of +.IR path , +and buflen will hold the number of bytes written excluding the NUL terminating +byte. +.sp +If both +.I buf +and +.I *buf +are not NULL, then +.I buf +is assumed to be a pre-allocated buffer of +.I *buflen +size. The files contents will be put into +.I *buf +and the number of bytes written to +.IR *buflen , +excluding the NUL terminating byte. In the case that +.I buflen +is too small to hold the NUL-terminated contents of the file, +.I buflen +will contain a suggested size for +.IR *buf . +This suggested size includes the NUL terminating byte, just as in the case where +.I buf +is NULL. + +.SH RETURN VALUES +.TP +.B 0 +on success +.TP +-ve +error code on failure and sets errno appropriately. + +.SH ERRORS +.TP +-EINVAL +if either +.I path +or +.I buflen +are NULL. +.TP +-EOVERFLOW +if +.I buf +and +.I *buf +are not NULL, and the pre-allocated buffer is too small. +.TP +-ENOMEM +if +.I buf +is not NULL, but the buffer allocation failed + +.SH SEE ALSO +.BR llapi_get_param_paths (3), +.BR lustreapi (7), +.BR lctl-get_param (8) diff --git a/lustre/include/lustre/lustreapi.h b/lustre/include/lustre/lustreapi.h index 39a8090..7ed703e 100644 --- a/lustre/include/lustre/lustreapi.h +++ b/lustre/include/lustre/lustreapi.h @@ -38,6 +38,7 @@ * @{ */ +#include #include #include #include @@ -1118,6 +1119,10 @@ int llapi_layout_sanity(struct llapi_layout *layout, bool incomplete, bool flr); void llapi_layout_sanity_perror(int error); int llapi_layout_dom_size(struct llapi_layout *layout, uint64_t *size); +int llapi_param_get_paths(const char *pattern, glob_t *paths); +int llapi_param_get_value(const char *path, char **buf, size_t *buflen); +void llapi_param_paths_free(glob_t *paths); + /** @} llapi */ #if defined(__cplusplus) diff --git a/lustre/tests/sanity.sh b/lustre/tests/sanity.sh index 3ff0118..18a1390 100755 --- a/lustre/tests/sanity.sh +++ b/lustre/tests/sanity.sh @@ -19690,12 +19690,22 @@ test_300d() { #local striped directory $LFS setdirstripe -i 0 -c 2 -H all_char $DIR/$tdir/striped_dir || error "set striped dir error" + #look at the directories for debug purposes + ls -l $DIR/$tdir + $LFS getdirstripe $DIR/$tdir + ls -l $DIR/$tdir/striped_dir + $LFS getdirstripe $DIR/$tdir/striped_dir createmany -o $DIR/$tdir/striped_dir/f 10 || error "create 10 files failed" #remote striped directory $LFS setdirstripe -i 1 -c 2 $DIR/$tdir/remote_striped_dir || error "set striped dir error" + #look at the directories for debug purposes + ls -l $DIR/$tdir + $LFS getdirstripe $DIR/$tdir + ls -l $DIR/$tdir/remote_striped_dir + $LFS getdirstripe $DIR/$tdir/remote_striped_dir createmany -o $DIR/$tdir/remote_striped_dir/f 10 || error "create 10 files failed" diff --git a/lustre/utils/liblustreapi_param.c b/lustre/utils/liblustreapi_param.c index 3dd79cd..8a8ae45 100644 --- a/lustre/utils/liblustreapi_param.c +++ b/lustre/utils/liblustreapi_param.c @@ -25,7 +25,13 @@ * Copyright (c) 2016, Intel Corporation. */ #include +#include #include +#include +#include +#include +#include +#include #include #include @@ -181,3 +187,239 @@ err: return rc; } + +int llapi_param_get_paths(const char *pattern, glob_t *paths) +{ + return get_lustre_param_path(NULL, NULL, FILTER_BY_NONE, + pattern, paths); +} + +/** + * Read to the end of the file and count the bytes read. + */ +static int bytes_remaining(int fd, size_t *file_size) +{ + int rc = 0; + size_t bytes_read = 0; + long page_size = sysconf(_SC_PAGESIZE); + char *temp_buf; + + temp_buf = malloc(page_size); + if (temp_buf == NULL) + return -ENOMEM; + + while (1) { + ssize_t count = read(fd, temp_buf, page_size); + + if (count == 0) { + *file_size = bytes_read; + break; + } + + if (count < 0) { + rc = -errno; + break; + } + bytes_read += count; + } + + free(temp_buf); + return rc; +} + +/** + * Determine the size of a file by reading it. + */ +static int required_size(const char *path, size_t *file_size) +{ + int rc = 0; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + rc = bytes_remaining(fd, file_size); + + close(fd); + *file_size += 1; + return rc; +} + +static +int copy_file_expandable(const char *path, char **buf, size_t *file_size) +{ + long page_size = sysconf(_SC_PAGESIZE); + int rc = 0; + char *temp_buf; + int fd; + FILE *fp; + + fp = open_memstream(buf, file_size); + if (fp == NULL) { + rc = -errno; + goto out; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + rc = -errno; + goto close_stream; + } + + temp_buf = calloc(1, page_size); + if (buf == NULL) { + rc = -ENOMEM; + goto close_file; + } + + while (1) { + ssize_t count = read(fd, temp_buf, page_size); + + if (count == 0) + break; + if (count < 0) { + rc = -errno; + break; + } + + if (fwrite(temp_buf, 1, count, fp) != count) { + rc = -errno; + break; + } + } + + free(temp_buf); +close_file: + close(fd); +close_stream: + fclose(fp); +out: + /* If rc != 0 and *buf != NULL, the caller may retry. + * This would likely result in copy_file_fixed() being called + * on accident, and a likely memory error. + */ + if (rc != 0) { + free(*buf); + *buf = NULL; + } + return rc; +} + +/** + * Copy file to a buffer and write the number of bytes copied + */ +static int copy_file_fixed(const char *path, char *buf, size_t *buflen) +{ + int rc = 0; + size_t bytes_read = 0; + size_t max_read = *buflen - 1; + size_t remaining = 0; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + while (bytes_read < max_read) { + ssize_t count = read(fd, + buf + bytes_read, + max_read - bytes_read); + + /* read the entire file */ + if (count == 0) { + *buflen = bytes_read + 1; + buf[bytes_read] = '\0'; + goto out; + } + + if (count < 0) + goto check_size; + + bytes_read += count; + } + +check_size: + /* need to check size in case error due to buf being too small + * for read() or exited loop due to buf being full + */ + buf[max_read] = '\0'; + + rc = bytes_remaining(fd, &remaining); + if (rc != 0) { + rc = -errno; + goto out; + } + *buflen = bytes_read + remaining; + + /* file was not (*buflen - 1) bytes, add 1 for reallocating */ + if (remaining != 0) { + *buflen += 1; + rc = -EOVERFLOW; + } + +out: + close(fd); + + return rc; +} + +/** + * Read the value of the file with location \a path + * into a buffer. + * + * \param path[in] the location of a parameter file + * \param buf[in,out] a pointer to a pointer to a buffer + * \param buflen[in,out] the length of a pre-allocated buffer + * when passed in, and either the number + * of bytes written or the suggested + * size of *buf when passed out. + * + * There are 3 behaviors based on the value of buf. + * If buf == NULL, then the buffer size needed to read the file at + * \a path will be written to \a *buflen. + * If \a buf != NULL and \a *buf == NULL, the value of *buf will point + * to a buffer that will be automatically sized to fit the file + * contents. A NUL byte will be added to the end of the buffer. + * The value of \a *buflen will be set to the number of bytes written + * excuding the NUL byte. + * If \a buf != NULL and \a *buf != NULL, it will be assumed that \a *buf + * points to a pre-allocated buffer with a capacity of \a *buflen. + * If there is sufficient space, the file contents and NUL terminating + * byte will be written to the buffer at .\a *buf. + * Otherwise, the required size of \a *buflen with be written to \a *buflen. + * + * Returns 0 for success with null terminated string in \a *buf. + * Returns negative errno value on error. + * For case of \a buf != NULL and \a *buf != NULL, a return value + * of -EOVERFLOW indicates that it's possible retry with a larger + * buffer. + */ +int llapi_param_get_value(const char *path, char **buf, size_t *buflen) +{ + int rc = 0; + + if (path == NULL || buflen == NULL) + rc = -EINVAL; + else if (buf == NULL) + rc = required_size(path, buflen); + /* handle for buffer, but no buffer + * create a buffer of the required size + */ + else if (*buf == NULL) + rc = copy_file_expandable(path, buf, buflen); + /* preallocated buffer given, attempt to copy + * file to it, return file size if buffer too small + */ + else + rc = copy_file_fixed(path, *buf, buflen); + + errno = -rc; + + return rc; +} + +void llapi_param_paths_free(glob_t *paths) +{ + cfs_free_param_data(paths); +} diff --git a/lustre/utils/lustre_cfg.c b/lustre/utils/lustre_cfg.c index a6a783b..60f2ef8 100644 --- a/lustre/utils/lustre_cfg.c +++ b/lustre/utils/lustre_cfg.c @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -835,74 +836,32 @@ char *parameter_opname[] = { static int read_param(const char *path, const char *param_name, struct param_opts *popt) { - bool display_path = popt->po_show_path; - long page_size = sysconf(_SC_PAGESIZE); int rc = 0; - char *buf; - int fd; - - /* Read the contents of file to stdout */ - fd = open(path, O_RDONLY); - if (fd < 0) { - rc = -errno; - fprintf(stderr, - "error: get_param: opening '%s': %s\n", - path, strerror(errno)); - return rc; - } + char *buf = NULL; + size_t buflen; - buf = calloc(1, page_size); - if (buf == NULL) { + rc = llapi_param_get_value(path, &buf, &buflen); + if (rc != 0) { fprintf(stderr, - "error: get_param: allocating '%s' buffer: %s\n", - path, strerror(errno)); - close(fd); - return -ENOMEM; + "error: read_param: \'%s\': %s\n", + path, strerror(-rc)); + goto free_buf; } + /* don't print anything for empty files */ + if (buf[0] == '\0') + goto free_buf; - while (1) { - ssize_t count = read(fd, buf, page_size); + if (popt->po_show_path) { + bool longbuf; - if (count == 0) - break; - if (count < 0) { - rc = -errno; - if (errno != EIO) { - fprintf(stderr, "error: get_param: " - "reading '%s': %s\n", - param_name, strerror(errno)); - } - break; - } - - /* Print the output in the format path=value if the value does - * not contain a new line character and the output can fit in - * a single line, else print value on new line */ - if (display_path) { - bool longbuf; - - longbuf = strnchr(buf, count - 1, '\n') != NULL || - count + strlen(param_name) >= 80; - printf("%s=%s", param_name, longbuf ? "\n" : buf); - - /* Make sure it doesn't print again while looping */ - display_path = false; - - if (!longbuf) - continue; - } - - if (fwrite(buf, 1, count, stdout) != count) { - rc = -errno; - fprintf(stderr, - "error: get_param: write to stdout: %s\n", - strerror(errno)); - break; - } + longbuf = strnchr(buf, buflen - 1, '\n') != NULL || + buflen + strlen(param_name) >= 80; + printf("%s=%s", param_name, longbuf ? "\n" : ""); } - close(fd); - free(buf); + printf("%s", buf); +free_buf: + free(buf); return rc; } @@ -976,7 +935,7 @@ param_display(struct param_opts *popt, char *pattern, char *value, char *opname = parameter_opname[mode]; int rc, i; - rc = cfs_get_param_paths(&paths, "%s", pattern); + rc = llapi_param_get_paths(pattern, &paths); if (rc != 0) { rc = -errno; if (!popt->po_recursive) { @@ -1136,7 +1095,7 @@ param_display(struct param_opts *popt, char *pattern, char *value, free(dup_cache[i]); free(dup_cache); out_param: - cfs_free_param_data(&paths); + llapi_param_paths_free(&paths); return rc; } -- 1.8.3.1