Changeset View
Standalone View
usr.bin/posixshmcontrol/posixshmcontrol.c
- This file was added.
/*- | |||||
* Copyright (c) 2019 The FreeBSD Foundation | |||||
* | |||||
* This software was developed by Konstantin Belousov <kib@FreeBSD.org> | |||||
* under sponsorship from the FreeBSD Foundation. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/mman.h> | |||||
#include <sys/stat.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/user.h> | |||||
#include <err.h> | |||||
#include <fcntl.h> | |||||
#include <grp.h> | |||||
#include <libutil.h> | |||||
#include <pwd.h> | |||||
#include <stdbool.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
static void | |||||
usage(void) | |||||
{ | |||||
fprintf(stderr, "Usage:\n" | |||||
"posixshmcontrol create [-m <mode>] <path> ...\n" | |||||
"posixshmcontrol rm <path> ...\n" | |||||
"posixshmcontrol ls [-h] [-n]\n" | |||||
tmunro: Forgot to mention -p <path> for -t. Erm, why not get rid of the -p and the -z anyway? -c… | |||||
"posixshmcontrol dump <path> ...\n" | |||||
"posixshmcontrol stat [-h] [-n] <path> ...\n" | |||||
Done Inline ActionsI like the subcommand syntax but it would be more consistent with mkdir, mknod, truncate, etc. to take mode and newlen as options and allow multiple pathnames at a time. jilles: I like the subcommand syntax but it would be more consistent with mkdir, mknod, truncate, etc. | |||||
"posixshmcontrol truncate [-s <newlen>] <path>\n"); | |||||
} | |||||
static int | |||||
create_one_shm(const char *path, long mode) | |||||
{ | |||||
int fd; | |||||
fd = shm_open(path, O_RDWR | O_CREAT, mode); | |||||
if (fd == -1) { | |||||
Done Inline ActionsShould be able to specify the mode too. tmunro: Should be able to specify the mode too. | |||||
warn("create %s", path); | |||||
return (1); | |||||
} | |||||
close(fd); | |||||
return (0); | |||||
} | |||||
static int | |||||
create_shm(int argc, char **argv) | |||||
{ | |||||
char *end; | |||||
long mode; | |||||
int c, i, ret, ret1; | |||||
mode = 0600; | |||||
while ((c = getopt(argc, argv, "m:")) != -1) { | |||||
switch (c) { | |||||
case 'm': | |||||
Done Inline ActionsShould this exit with a non-zero status? jilles: Should this exit with a non-zero status? | |||||
errno = 0; | |||||
mode = strtol(optarg, &end, 0); | |||||
if (mode == 0 && errno != 0) | |||||
err(1, "mode:"); | |||||
if (*end != '\0') | |||||
errx(1, "non-integer mode"); | |||||
break; | |||||
case '?': | |||||
default: | |||||
usage(); | |||||
return (2); | |||||
} | |||||
} | |||||
argc -= optind; | |||||
argv += optind; | |||||
ret = 0; | |||||
for (i = 0; i < argc; i++) { | |||||
ret1 = create_one_shm(argv[i], mode); | |||||
if (ret1 != 0 && ret == 0) | |||||
ret = ret1; | |||||
} | |||||
return (ret); | |||||
} | |||||
Not Done Inline ActionsThis only returns shared memory objects that have names, not unlinked or SHM_ANON objects that are still open. In some way, that makes sense (comparing to ls), but objects without names still consume memory. jilles: This only returns shared memory objects that have names, not unlinked or `SHM_ANON` objects… | |||||
Done Inline ActionsThis was deliberate, I did considered handling them. This would require more extensive kernel changes to track such objects, which I do not want to do in the scope of this work. One of the reasons which convinced me to not consider anon handling as required is that such objects do disappear when the process owning fd is killed. In other words, they are not too persistent (unlike SysV ipc objects or named posix shm). kib: This was deliberate, I did considered handling them. This would require more extensive kernel… | |||||
static int | |||||
delete_one_shm(const char *path) | |||||
{ | |||||
int error, ret; | |||||
error = shm_unlink(path); | |||||
if (error != 0) { | |||||
warn("unlink of %s failed", path); | |||||
ret = 1; | |||||
} else { | |||||
ret = 0; | |||||
} | |||||
return (ret); | |||||
} | |||||
static int | |||||
delete_shm(int argc, char **argv) | |||||
Done Inline ActionsShould we print the mode as eg --rwarwarwa like ipcs? Also it's a bit ugly to have the path unaligned, unlike ipcs's line up output. tmunro: Should we print the mode as eg --rwarwarwa like ipcs? Also it's a bit ugly to have the path… | |||||
{ | |||||
int i, ret, ret1; | |||||
ret = 0; | |||||
for (i = 1; i < argc; i++) { | |||||
ret1 = delete_one_shm(argv[i]); | |||||
if (ret1 != 0 && ret == 0) | |||||
ret = ret1; | |||||
} | |||||
return (ret); | |||||
} | |||||
static const char listmib[] = "kern.ipc.posix_shm_list"; | |||||
static void | |||||
shm_decode_mode(mode_t m, char *str) | |||||
{ | |||||
int i; | |||||
i = 0; | |||||
str[i++] = (m & S_IRUSR) != 0 ? 'r' : '-'; | |||||
Done Inline Actions%zd is not the correct formatting specifier for uint64_t on 32-bit systems. jilles: `%zd` is not the correct formatting specifier for `uint64_t` on 32-bit systems. | |||||
str[i++] = (m & S_IWUSR) != 0 ? 'w' : '-'; | |||||
str[i++] = (m & S_IXUSR) != 0 ? 'x' : '-'; | |||||
str[i++] = (m & S_IRGRP) != 0 ? 'r' : '-'; | |||||
str[i++] = (m & S_IWGRP) != 0 ? 'w' : '-'; | |||||
str[i++] = (m & S_IXGRP) != 0 ? 'x' : '-'; | |||||
str[i++] = (m & S_IROTH) != 0 ? 'r' : '-'; | |||||
str[i++] = (m & S_IWOTH) != 0 ? 'w' : '-'; | |||||
str[i++] = (m & S_IXOTH) != 0 ? 'x' : '-'; | |||||
str[i] = '\0'; | |||||
} | |||||
static int | |||||
list_shm(int argc, char **argv) | |||||
{ | |||||
Done Inline ActionsThis loop is wrong, I'm only getting 4096 bytes out and a bogus "No error" warning. Also redirecting output to /dev/null gives me "Inappropriate ioctl for device", not sure what's happening there. tmunro: This loop is wrong, I'm only getting 4096 bytes out and a bogus "No error" warning. Also… | |||||
char *buf, *bp, sizebuf[8], str[10], c; | |||||
const struct kinfo_file *kif; | |||||
struct stat st; | |||||
int error, fd, mib[3], ret; | |||||
size_t len, miblen; | |||||
bool hsize, uname; | |||||
hsize = false; | |||||
uname = true; | |||||
while ((c = getopt(argc, argv, "hn")) != -1) { | |||||
switch (c) { | |||||
case 'h': | |||||
hsize = true; | |||||
break; | |||||
case 'n': | |||||
uname = false; | |||||
break; | |||||
default: | |||||
usage(); | |||||
return (2); | |||||
} | |||||
} | |||||
if (argc != optind) { | |||||
usage(); | |||||
return (2); | |||||
} | |||||
miblen = nitems(mib); | |||||
error = sysctlnametomib(listmib, mib, &miblen); | |||||
if (error == -1) { | |||||
warn("cannot translate %s", listmib); | |||||
return (1); | |||||
Done Inline ActionsI think we should show shm_uid and shm_guid too, otherwise mode is a bit meaningless in this list. I realise that you can get those with "posixshmcontrol stat", but ipcs shows OWNER and GROUP columns beside MODE. tmunro: I think we should show shm_uid and shm_guid too, otherwise mode is a bit meaningless in this… | |||||
} | |||||
len = 0; | |||||
error = sysctl(mib, miblen, NULL, &len, NULL, 0); | |||||
if (error == -1) { | |||||
warn("cannot get %s length", listmib); | |||||
return (1); | |||||
} | |||||
len = len * 4 / 3; | |||||
buf = malloc(len); | |||||
if (buf == NULL) { | |||||
warn("malloc"); | |||||
return (1); | |||||
} | |||||
error = sysctl(mib, miblen, buf, &len, NULL, 0); | |||||
if (error != 0) { | |||||
warn("reading %s", listmib); | |||||
Done Inline ActionsPerhaps %#o or shm_decode_mode are a bit clearer. jilles: Perhaps `%#o` or `shm_decode_mode` are a bit clearer. | |||||
ret = 1; | |||||
goto out; | |||||
} | |||||
ret = 0; | |||||
Done Inline Actions%zd is not the correct formatting specifier for off_t on 32-bit systems. jilles: `%zd` is not the correct formatting specifier for `off_t` on 32-bit systems. | |||||
printf("MODE \tOWNER\tGROUP\tSIZE\tPATH\n"); | |||||
for (bp = buf; bp < buf + len; bp += kif->kf_structsize) { | |||||
kif = (const struct kinfo_file *)(void *)bp; | |||||
if (kif->kf_structsize == 0) | |||||
Done Inline ActionsPerhaps we should print the nanoseconds part as well using .%09ld format? jilles: Perhaps we should print the nanoseconds part as well using `.%09ld` format? | |||||
break; | |||||
fd = shm_open(kif->kf_path, O_RDONLY, 0); | |||||
if (fd == -1) { | |||||
warn("open %s", kif->kf_path); | |||||
ret = 1; | |||||
continue; | |||||
} | |||||
error = fstat(fd, &st); | |||||
close(fd); | |||||
if (error != 0) { | |||||
warn("stat %s", kif->kf_path); | |||||
ret = 1; | |||||
continue; | |||||
} | |||||
shm_decode_mode(kif->kf_un.kf_file.kf_file_mode, str); | |||||
printf("%s\t", str); | |||||
if (uname) { | |||||
printf("%s\t%s\t", user_from_uid(st.st_uid, 0), | |||||
group_from_gid(st.st_gid, 0)); | |||||
} else { | |||||
printf("%d\t%d\t", st.st_uid, st.st_gid); | |||||
} | |||||
if (hsize) { | |||||
humanize_number(sizebuf, sizeof(sizebuf), | |||||
kif->kf_un.kf_file.kf_file_size, "", HN_AUTOSCALE, | |||||
HN_NOSPACE); | |||||
printf("%s\t", sizebuf); | |||||
} else { | |||||
printf("%jd\t", | |||||
(uintmax_t)kif->kf_un.kf_file.kf_file_size); | |||||
} | |||||
printf("%s\n", kif->kf_path); | |||||
} | |||||
out: | |||||
free(buf); | |||||
return (ret); | |||||
} | |||||
static int | |||||
read_one_shm(const char *path) | |||||
{ | |||||
char buf[4096]; | |||||
ssize_t size, se; | |||||
int fd, ret; | |||||
ret = 1; | |||||
fd = shm_open(path, O_RDONLY, 0); | |||||
if (fd == -1) { | |||||
warn("open %s", path); | |||||
goto out; | |||||
} | |||||
for (;;) { | |||||
size = read(fd, buf, sizeof(buf)); | |||||
if (size > 0) { | |||||
se = fwrite(buf, 1, size, stdout); | |||||
if (se < size) { | |||||
warnx("short write to stdout"); | |||||
goto out; | |||||
} | |||||
} | |||||
if (size == (ssize_t)sizeof(buf)) | |||||
continue; | |||||
if (size >= 0 && size < (ssize_t)sizeof(buf)) { | |||||
ret = 0; | |||||
goto out; | |||||
} | |||||
warn("read from %s", path); | |||||
goto out; | |||||
} | |||||
out: | |||||
close(fd); | |||||
return (ret); | |||||
} | |||||
static int | |||||
read_shm(int argc, char **argv) | |||||
{ | |||||
int i, ret, ret1; | |||||
ret = 0; | |||||
for (i = 1; i < argc; i++) { | |||||
ret1 = read_one_shm(argv[i]); | |||||
if (ret1 != 0 && ret == 0) | |||||
ret = ret1; | |||||
} | |||||
return (ret); | |||||
} | |||||
static int | |||||
stat_one_shm(const char *path, bool hsize, bool uname) | |||||
{ | |||||
char sizebuf[8]; | |||||
struct stat st; | |||||
int error, fd, ret; | |||||
fd = shm_open(path, O_RDONLY, 0); | |||||
if (fd == -1) { | |||||
warn("open %s", path); | |||||
return (1); | |||||
} | |||||
ret = 0; | |||||
error = fstat(fd, &st); | |||||
if (error == -1) { | |||||
warn("stat %s", path); | |||||
ret = 1; | |||||
} else { | |||||
printf("path\t%s\n", path); | |||||
printf("inode\t%jd\n", (uintmax_t)st.st_ino); | |||||
printf("mode\t%#o\n", st.st_mode); | |||||
printf("nlink\t%jd\n", (uintmax_t)st.st_nlink); | |||||
if (uname) { | |||||
printf("owner\t%s\n", user_from_uid(st.st_uid, 0)); | |||||
printf("group\t%s\n", group_from_gid(st.st_gid, 0)); | |||||
} else { | |||||
printf("uid\t%d\n", st.st_uid); | |||||
printf("gid\t%d\n", st.st_gid); | |||||
} | |||||
if (hsize) { | |||||
humanize_number(sizebuf, sizeof(sizebuf), | |||||
st.st_size, "", HN_AUTOSCALE, HN_NOSPACE); | |||||
printf("size\t%s\n", sizebuf); | |||||
} else { | |||||
printf("size\t%jd\n", (uintmax_t)st.st_size); | |||||
} | |||||
printf("atime\t%ld.%09ld\n", (long)st.st_atime, | |||||
(long)st.st_atim.tv_nsec); | |||||
printf("mtime\t%ld.%09ld\n", (long)st.st_mtime, | |||||
(long)st.st_mtim.tv_nsec); | |||||
printf("ctime\t%ld.%09ld\n", (long)st.st_ctime, | |||||
(long)st.st_ctim.tv_nsec); | |||||
printf("birth\t%ld.%09ld\n", (long)st.st_birthtim.tv_sec, | |||||
(long)st.st_birthtim.tv_nsec); | |||||
} | |||||
close(fd); | |||||
return (ret); | |||||
} | |||||
static int | |||||
stat_shm(int argc, char **argv) | |||||
{ | |||||
int c, i, ret, ret1; | |||||
bool hsize, uname; | |||||
hsize = false; | |||||
uname = true; | |||||
while ((c = getopt(argc, argv, "hn")) != -1) { | |||||
switch (c) { | |||||
case 'h': | |||||
hsize = true; | |||||
break; | |||||
case 'n': | |||||
uname = false; | |||||
break; | |||||
default: | |||||
usage(); | |||||
return (2); | |||||
} | |||||
} | |||||
argc -= optind; | |||||
argv += optind; | |||||
ret = 0; | |||||
for (i = 0; i < argc; i++) { | |||||
ret1 = stat_one_shm(argv[i], hsize, uname); | |||||
if (ret1 != 0 && ret == 0) | |||||
ret = ret1; | |||||
} | |||||
return (ret); | |||||
} | |||||
static int | |||||
truncate_one_shm(const char *path, uint64_t newsize) | |||||
{ | |||||
int error, fd, ret; | |||||
ret = 0; | |||||
fd = shm_open(path, O_RDWR, 0); | |||||
if (fd == -1) { | |||||
warn("open %s", path); | |||||
return (1); | |||||
} | |||||
error = ftruncate(fd, newsize); | |||||
if (error == -1) { | |||||
warn("truncate %s", path); | |||||
ret = 1; | |||||
} | |||||
close(fd); | |||||
return (ret); | |||||
} | |||||
static int | |||||
truncate_shm(int argc, char **argv) | |||||
{ | |||||
uint64_t newsize; | |||||
int c, i, ret, ret1; | |||||
newsize = 0; | |||||
while ((c = getopt(argc, argv, "s:")) != -1) { | |||||
switch (c) { | |||||
case 's': | |||||
if (expand_number(optarg, &newsize) == -1) | |||||
err(1, "size:"); | |||||
break; | |||||
case '?': | |||||
default: | |||||
return (2); | |||||
} | |||||
} | |||||
argc -= optind; | |||||
argv += optind; | |||||
ret = 0; | |||||
for (i = 0; i < argc; i++) { | |||||
ret1 = truncate_one_shm(argv[i], newsize); | |||||
if (ret1 != 0 && ret == 0) | |||||
ret = ret1; | |||||
} | |||||
return (ret); | |||||
} | |||||
struct opmode { | |||||
const char *cmd; | |||||
int (*impl)(int argc, char **argv); | |||||
}; | |||||
static const struct opmode opmodes[] = { | |||||
{ .cmd = "create", .impl = create_shm}, | |||||
{ .cmd = "rm", .impl = delete_shm, }, | |||||
{ .cmd = "ls", .impl = list_shm }, | |||||
{ .cmd = "dump", .impl = read_shm, }, | |||||
{ .cmd = "stat", .impl = stat_shm, }, | |||||
{ .cmd = "truncate", .impl = truncate_shm, }, | |||||
}; | |||||
int | |||||
main(int argc, char *argv[]) | |||||
{ | |||||
const struct opmode *opmode; | |||||
int i, ret; | |||||
ret = 0; | |||||
opmode = NULL; | |||||
if (argc < 2) { | |||||
usage(); | |||||
exit(2); | |||||
} | |||||
for (i = 0; i < (int)nitems(opmodes); i++) { | |||||
if (strcmp(argv[1], opmodes[i].cmd) == 0) { | |||||
opmode = &opmodes[i]; | |||||
break; | |||||
} | |||||
} | |||||
if (opmode == NULL) { | |||||
usage(); | |||||
exit(2); | |||||
} | |||||
ret = opmode->impl(argc - 1, argv + 1); | |||||
exit(ret); | |||||
} |
Forgot to mention -p <path> for -t. Erm, why not get rid of the -p and the -z anyway? -c <path>, -t 1234 <path>