Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5d75987
Adding testing facility
SupraSummus Jun 10, 2018
ce1f09a
Terminate gracefuly when SIGINT or SIGTERM is received
SupraSummus Jun 10, 2018
65b1faf
Actually abort test when device is in use + don't print irrelevant ou…
SupraSummus Jun 10, 2018
1496a07
Return unsucessful exit codes when errors are detected
SupraSummus Jun 10, 2018
a4722c7
Merge pull request #17 from SupraSummus/test
Jun 18, 2018
a8a3ad2
Merge pull request #18 from SupraSummus/signal
Jun 18, 2018
5e62594
Merge pull request #19 from SupraSummus/error
Jun 18, 2018
ac5f0b2
Test fixes
SupraSummus Jun 12, 2018
79d49fe
Moved nbd socket serving loop to separate function
SupraSummus Jun 12, 2018
ad47f71
README description of how to terminate BUSE
SupraSummus Jun 18, 2018
e680a1c
Merge pull request #20 from SupraSummus/test
Jun 18, 2018
581f8a5
Merge pull request #22 from SupraSummus/readme
Jun 18, 2018
86652c6
Merge pull request #21 from SupraSummus/refactor
Jun 18, 2018
b8bfacf
Removed device test-open
SupraSummus Jun 18, 2018
caf7bd3
Child process should exit instead of return
SupraSummus Jun 18, 2018
a5dc0a7
Parent process waits for child process to exit
SupraSummus Jun 18, 2018
8be3839
Merge pull request #23 from SupraSummus/refactor
Jun 19, 2018
c62b358
Merge pull request #24 from SupraSummus/fix
Jun 19, 2018
336cb77
Cleanup imports
SupraSummus Jun 19, 2018
a161cfe
Merge pull request #25 from SupraSummus/refactor
Jun 20, 2018
aeceb3a
Debug switch
SupraSummus Jun 22, 2018
b58c1c1
busexmp cmdline argument parsing
SupraSummus Jun 22, 2018
799ebe6
Merge pull request #26 from SupraSummus/debug
Jun 25, 2018
09bbe1a
Merge pull request #27 from SupraSummus/busexmp
Jun 25, 2018
6cd937f
Flush support is now reported to the NBD client.
albertofaria Jun 28, 2018
b4a7f53
Merge pull request #28 from albertofaria/master
Jun 29, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ OBJS := $(TARGET:=.o) $(LIBOBJS)
STATIC_LIB := libbuse.a

CC := /usr/bin/gcc
CFLAGS := -g -pedantic -Wall -Wextra -std=c99
override CFLAGS += -g -pedantic -Wall -Wextra -std=c99
LDFLAGS := -L. -lbuse

.PHONY: all clean
.PHONY: all clean test
all: $(TARGET)

$(TARGET): %: %.o $(STATIC_LIB)
Expand All @@ -22,5 +22,9 @@ $(STATIC_LIB): $(LIBOBJS)
$(LIBOBJS): %.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<

test: $(TARGET)
PATH=$(PWD):$$PATH sudo test/busexmp.sh
PATH=$(PWD):$$PATH sudo test/signal_termination.sh

clean:
rm -f $(TARGET) $(OBJS) $(STATIC_LIB)
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,40 @@ user.

## Running the Example Code

BUSE comes with an example driver in `busexmp.c` that implements a 128 MB
BUSE comes with an example driver in `busexmp.c` that implements a
memory disk. To try out the example code, run `make` and then execute the
following as root:

modprobe nbd
./busexmp /dev/nbd0
./busexmp 128M /dev/nbd0

You should then have an in-memory disk running, represented by the device file
`/dev/nbd0`. You can create a file system on the virtual disk, mount it, and
start reading and writing files on it:

mkfs.ext4 /dev/nbd0
mount /dev/nbd0 /mnt

BUSE should gracefuly disconnect from block device upon receiving SIGINT
or SIGTERM. However, if something goes wrong, block device is stuck in
unusable state and BUSE process exited or hung you can request
disconnect by:

nbd-client -d /dev/nbd0

Actually this command performs clean disconnect and can also be used
to terminate running instance of BUSE.

## Tests

To perform checks you can run scripts in `test/` directory. They require:
* superuser previlages,
* nbd kernel module loaded,
* BUSE and nbd (`nbd-client`) binaries in PATH.

`make test` will run all test scripts with BUSE added to PATH and using
sudo to grant permissions.

To increase verbosity define `BUSE_DEBUG`. You can do this in make command:

make test CFLAGS=-DBUSE_DEBUG
252 changes: 176 additions & 76 deletions buse.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,30 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#define _POSIX_C_SOURCE (200809L)

#include <assert.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <linux/types.h>
#include <linux/nbd.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#include "buse.h"

#ifndef BUSE_DEBUG
#define BUSE_DEBUG (0)
#endif

/*
* These helper functions were taken from cliserv.h in the nbd distribution.
*/
Expand Down Expand Up @@ -80,82 +89,39 @@ static int write_all(int fd, char* buf, size_t count)
return 0;
}

int buse_main(const char* dev_file, const struct buse_operations *aop, void *userdata)
{
int sp[2];
int nbd, sk, err, tmp_fd;
/* Signal handler to gracefully disconnect from nbd kernel driver. */
static int nbd_dev_to_disconnect = -1;
static void disconnect_nbd(int signal) {
(void)signal;
if (nbd_dev_to_disconnect != -1) {
if(ioctl(nbd_dev_to_disconnect, NBD_DISCONNECT) == -1) {
warn("failed to request disconect on nbd device");
} else {
nbd_dev_to_disconnect = -1;
fprintf(stderr, "sucessfuly requested disconnect on nbd device\n");
}
}
}

/* Sets signal action like regular sigaction but is suspicious. */
static int set_sigaction(int sig, const struct sigaction * act) {
struct sigaction oact;
int r = sigaction(sig, act, &oact);
if (r == 0 && oact.sa_handler != SIG_DFL) {
warnx("overriden non-default signal handler (%d: %s)", sig, strsignal(sig));
}
return r;
}

/* Serve userland side of nbd socket. If everything worked ok, return 0. */
static int serve_nbd(int sk, const struct buse_operations * aop, void * userdata) {
u_int64_t from;
u_int32_t len;
ssize_t bytes_read;
struct nbd_request request;
struct nbd_reply reply;
void *chunk;

err = socketpair(AF_UNIX, SOCK_STREAM, 0, sp);
assert(!err);

nbd = open(dev_file, O_RDWR);
if (nbd == -1) {
fprintf(stderr,
"Failed to open `%s': %s\n"
"Is kernel module `nbd' is loaded and you have permissions "
"to access the device?\n", dev_file, strerror(errno));
return 1;
}

if (aop->blksize) {
err = ioctl(nbd, NBD_SET_BLKSIZE, aop->blksize);
assert(err != -1);
}
if (aop->size) {
err = ioctl(nbd, NBD_SET_SIZE, aop->size);
assert(err != -1);
}
if (aop->size_blocks) {
err = ioctl(nbd, NBD_SET_SIZE_BLOCKS, aop->size_blocks);
assert(err != -1);
}

err = ioctl(nbd, NBD_CLEAR_SOCK);
assert(err != -1);

if (!fork()) {
/* The child needs to continue setting things up. */
close(sp[0]);
sk = sp[1];

if(ioctl(nbd, NBD_SET_SOCK, sk) == -1){
fprintf(stderr, "ioctl(nbd, NBD_SET_SOCK, sk) failed.[%s]\n", strerror(errno));
}
#if defined NBD_SET_FLAGS && defined NBD_FLAG_SEND_TRIM
else if(ioctl(nbd, NBD_SET_FLAGS, NBD_FLAG_SEND_TRIM) == -1){
fprintf(stderr, "ioctl(nbd, NBD_SET_FLAGS, NBD_FLAG_SEND_TRIM) failed.[%s]\n", strerror(errno));
}
#endif
else{
err = ioctl(nbd, NBD_DO_IT);
fprintf(stderr, "nbd device terminated with code %d\n", err);
if (err == -1)
fprintf(stderr, "%s\n", strerror(errno));
}

ioctl(nbd, NBD_CLEAR_QUE);
ioctl(nbd, NBD_CLEAR_SOCK);

exit(0);
}

/* The parent opens the device file at least once, to make sure the
* partition table is updated. Then it closes it and starts serving up
* requests. */

tmp_fd = open(dev_file, O_RDONLY);
assert(tmp_fd != -1);
close(tmp_fd);

close(sp[1]);
sk = sp[0];

reply.magic = htonl(NBD_REPLY_MAGIC);
reply.error = htonl(0);

Expand All @@ -175,7 +141,7 @@ int buse_main(const char* dev_file, const struct buse_operations *aop, void *use
* and writes.
*/
case NBD_CMD_READ:
fprintf(stderr, "Request for read of size %d\n", len);
if (BUSE_DEBUG) fprintf(stderr, "Request for read of size %d\n", len);
/* Fill with zero in case actual read is not implemented */
chunk = malloc(len);
if (aop->read) {
Expand All @@ -190,7 +156,7 @@ int buse_main(const char* dev_file, const struct buse_operations *aop, void *use
free(chunk);
break;
case NBD_CMD_WRITE:
fprintf(stderr, "Request for write of size %d\n", len);
if (BUSE_DEBUG) fprintf(stderr, "Request for write of size %d\n", len);
chunk = malloc(len);
read_all(sk, chunk, len);
if (aop->write) {
Expand All @@ -203,13 +169,15 @@ int buse_main(const char* dev_file, const struct buse_operations *aop, void *use
write_all(sk, (char*)&reply, sizeof(struct nbd_reply));
break;
case NBD_CMD_DISC:
if (BUSE_DEBUG) fprintf(stderr, "Got NBD_CMD_DISC\n");
/* Handle a disconnect request. */
if (aop->disc) {
aop->disc(userdata);
}
return 0;
return EXIT_SUCCESS;
#ifdef NBD_FLAG_SEND_FLUSH
case NBD_CMD_FLUSH:
if (BUSE_DEBUG) fprintf(stderr, "Got NBD_CMD_FLUSH\n");
if (aop->flush) {
reply.error = aop->flush(userdata);
}
Expand All @@ -218,6 +186,7 @@ int buse_main(const char* dev_file, const struct buse_operations *aop, void *use
#endif
#ifdef NBD_FLAG_SEND_TRIM
case NBD_CMD_TRIM:
if (BUSE_DEBUG) fprintf(stderr, "Got NBD_CMD_TRIM\n");
if (aop->trim) {
reply.error = aop->trim(from, len, userdata);
}
Expand All @@ -228,7 +197,138 @@ int buse_main(const char* dev_file, const struct buse_operations *aop, void *use
assert(0);
}
}
if (bytes_read == -1)
fprintf(stderr, "%s\n", strerror(errno));
return 0;
if (bytes_read == -1) {
warn("error reading userside of nbd socket");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

int buse_main(const char* dev_file, const struct buse_operations *aop, void *userdata)
{
int sp[2];
int nbd, sk, err, flags;

err = socketpair(AF_UNIX, SOCK_STREAM, 0, sp);
assert(!err);

nbd = open(dev_file, O_RDWR);
if (nbd == -1) {
fprintf(stderr,
"Failed to open `%s': %s\n"
"Is kernel module `nbd' loaded and you have permissions "
"to access the device?\n", dev_file, strerror(errno));
return 1;
}

if (aop->blksize) {
err = ioctl(nbd, NBD_SET_BLKSIZE, aop->blksize);
assert(err != -1);
}
if (aop->size) {
err = ioctl(nbd, NBD_SET_SIZE, aop->size);
assert(err != -1);
}
if (aop->size_blocks) {
err = ioctl(nbd, NBD_SET_SIZE_BLOCKS, aop->size_blocks);
assert(err != -1);
}

err = ioctl(nbd, NBD_CLEAR_SOCK);
assert(err != -1);

pid_t pid = fork();
if (pid == 0) {
/* Block all signals to not get interrupted in ioctl(NBD_DO_IT), as
* it seems there is no good way to handle such interruption.*/
sigset_t sigset;
if (
sigfillset(&sigset) != 0 ||
sigprocmask(SIG_SETMASK, &sigset, NULL) != 0
) {
warn("failed to block signals in child");
exit(EXIT_FAILURE);
}

/* The child needs to continue setting things up. */
close(sp[0]);
sk = sp[1];

if(ioctl(nbd, NBD_SET_SOCK, sk) == -1){
fprintf(stderr, "ioctl(nbd, NBD_SET_SOCK, sk) failed.[%s]\n", strerror(errno));
exit(EXIT_FAILURE);
}
else{
#if defined NBD_SET_FLAGS
flags = 0;
#if defined NBD_FLAG_SEND_TRIM
flags |= NBD_FLAG_SEND_TRIM;
#endif
#if defined NBD_FLAG_SEND_FLUSH
flags |= NBD_FLAG_SEND_FLUSH;
#endif
if (flags != 0 && ioctl(nbd, NBD_SET_FLAGS, flags) == -1){
fprintf(stderr, "ioctl(nbd, NBD_SET_FLAGS, %d) failed.[%s]\n", flags, strerror(errno));
exit(EXIT_FAILURE);
}
#endif
err = ioctl(nbd, NBD_DO_IT);
if (BUSE_DEBUG) fprintf(stderr, "nbd device terminated with code %d\n", err);
if (err == -1) {
warn("NBD_DO_IT terminated with error");
exit(EXIT_FAILURE);
}
}

if (
ioctl(nbd, NBD_CLEAR_QUE) == -1 ||
ioctl(nbd, NBD_CLEAR_SOCK) == -1
) {
warn("failed to perform nbd cleanup actions");
exit(EXIT_FAILURE);
}

exit(0);
}

/* Parent handles termination signals by terminating nbd device. */
assert(nbd_dev_to_disconnect == -1);
nbd_dev_to_disconnect = nbd;
struct sigaction act;
act.sa_handler = disconnect_nbd;
act.sa_flags = SA_RESTART;
if (
sigemptyset(&act.sa_mask) != 0 ||
sigaddset(&act.sa_mask, SIGINT) != 0 ||
sigaddset(&act.sa_mask, SIGTERM) != 0
) {
warn("failed to prepare signal mask in parent");
return EXIT_FAILURE;
}
if (
set_sigaction(SIGINT, &act) != 0 ||
set_sigaction(SIGTERM, &act) != 0
) {
warn("failed to register signal handlers in parent");
return EXIT_FAILURE;
}

close(sp[1]);

/* serve NBD socket */
int status;
status = serve_nbd(sp[0], aop, userdata);
if (close(sp[0]) != 0) warn("problem closing server side nbd socket");
if (status != 0) return status;

/* wait for subprocess */
if (waitpid(pid, &status, 0) == -1) {
warn("waitpid failed");
return EXIT_FAILURE;
}
if (WEXITSTATUS(status) != 0) {
return WEXITSTATUS(status);
}

return EXIT_SUCCESS;
}
Loading