diff --git a/headers/private/file_systems/ram_disk/ram_disk.h b/headers/private/file_systems/ram_disk/ram_disk.h new file mode 100644 index 0000000000..0a8279a38f --- /dev/null +++ b/headers/private/file_systems/ram_disk/ram_disk.h @@ -0,0 +1,47 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef _PRIVATE_FILE_SYSTEMS_RAM_DISK_RAM_DISK_H +#define _PRIVATE_FILE_SYSTEMS_RAM_DISK_RAM_DISK_H + + +#include <Drivers.h> +#include <StorageDefs.h> + + +#define RAM_DISK_CONTROL_DEVICE_NAME "disk/virtual/ram/control" +#define RAM_DISK_RAW_DEVICE_BASE_NAME "disk/virtual/ram" + + +enum { + RAM_DISK_IOCTL_REGISTER = B_DEVICE_OP_CODES_END + 1, + RAM_DISK_IOCTL_UNREGISTER, + RAM_DISK_IOCTL_FLUSH, + RAM_DISK_IOCTL_INFO +}; + + +struct ram_disk_ioctl_register { + uint64 size; + char path[B_PATH_NAME_LENGTH]; + + // return value + int32 id; +}; + + +struct ram_disk_ioctl_unregister { + int32 id; +}; + + +struct ram_disk_ioctl_info { + // return values + int32 id; + uint64 size; + char path[B_PATH_NAME_LENGTH]; +}; + + +#endif // _PRIVATE_FILE_SYSTEMS_RAM_DISK_RAM_DISK_H diff --git a/src/add-ons/kernel/drivers/disk/virtual/ram_disk/ram_disk.cpp b/src/add-ons/kernel/drivers/disk/virtual/ram_disk/ram_disk.cpp index 529b61b3b4..8807d2757e 100644 --- a/src/add-ons/kernel/drivers/disk/virtual/ram_disk/ram_disk.cpp +++ b/src/add-ons/kernel/drivers/disk/virtual/ram_disk/ram_disk.cpp @@ -4,6 +4,8 @@ */ +#include <file_systems/ram_disk/ram_disk.h> + #include <ctype.h> #include <errno.h> #include <fcntl.h> @@ -50,12 +52,12 @@ static const char* const kControlDeviceModuleName static const char* const kRawDeviceModuleName = "drivers/disk/virtual/ram_disk/raw/device_v1"; -static const char* const kControlDeviceName - = "disk/virtual/ram/control"; -static const char* const kRawDeviceBaseName = "disk/virtual/ram"; +static const char* const kControlDeviceName = RAM_DISK_CONTROL_DEVICE_NAME; +static const char* const kRawDeviceBaseName = RAM_DISK_RAW_DEVICE_BASE_NAME; static const char* const kFilePathItem = "ram_disk/file_path"; static const char* const kDeviceSizeItem = "ram_disk/device_size"; +static const char* const kDeviceIDItem = "ram_disk/id"; struct RawDevice; @@ -65,6 +67,11 @@ struct device_manager_info* sDeviceManager; static RawDeviceList sDeviceList; static mutex sDeviceListLock = MUTEX_INITIALIZER("ram disk device list"); +static uint64 sUsedRawDeviceIDs = 0; + + +static int32 allocate_raw_device_id(); +static void free_raw_device_id(int32 id); struct Device { @@ -100,19 +107,37 @@ struct ControlDevice : Device { { } - status_t Register(const char* filePath, uint64 deviceSize) + status_t Register(const char* filePath, uint64 deviceSize, int32& _id) { + int32 id = allocate_raw_device_id(); + if (id < 0) + return B_BUSY; + device_attr attrs[] = { {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {string: "RAM Disk Raw Device"}}, - {kFilePathItem, B_STRING_TYPE, {string: filePath}}, {kDeviceSizeItem, B_UINT64_TYPE, {ui64: deviceSize}}, + {kDeviceIDItem, B_UINT32_TYPE, {ui32: (uint32)id}}, + {kFilePathItem, B_STRING_TYPE, {string: filePath}}, {NULL} }; - return sDeviceManager->register_node( + // If filePath is NULL, remove the attribute. + if (filePath == NULL) { + size_t count = sizeof(attrs) / sizeof(attrs[0]); + memset(attrs + count - 2, 0, sizeof(attrs[0])); + } + + status_t error = sDeviceManager->register_node( sDeviceManager->get_parent_node(Node()), kDriverModuleName, attrs, NULL, NULL); + if (error != B_OK) { + free_raw_device_id(id); + return error; + } + + _id = id; + return B_OK; } virtual status_t PublishDevice() @@ -127,7 +152,8 @@ struct RawDevice : Device, DoublyLinkedListLinkImpl<RawDevice> { RawDevice(device_node* node) : Device(node), - fIndex(-1), + fID(-1), + fUnregistered(false), fDeviceSize(0), fDeviceName(NULL), fFilePath(NULL), @@ -139,7 +165,7 @@ struct RawDevice : Device, DoublyLinkedListLinkImpl<RawDevice> { virtual ~RawDevice() { - if (fIndex >= 0) { + if (fID >= 0) { MutexLocker locker(sDeviceListLock); sDeviceList.Remove(this); } @@ -148,12 +174,20 @@ struct RawDevice : Device, DoublyLinkedListLinkImpl<RawDevice> { free(fFilePath); } - int32 Index() const { return fIndex; } + int32 ID() const { return fID; } off_t DeviceSize() const { return fDeviceSize; } const char* DeviceName() const { return fDeviceName; } - status_t Init(const char* filePath, uint64 deviceSize) + bool IsUnregistered() const { return fUnregistered; } + + void SetUnregistered(bool unregistered) { + fUnregistered = unregistered; + } + + status_t Init(int32 id, const char* filePath, uint64 deviceSize) + { + fID = id; fFilePath = filePath != NULL ? strdup(filePath) : NULL; if (filePath != NULL && fFilePath == NULL) return B_NO_MEMORY; @@ -167,23 +201,10 @@ struct RawDevice : Device, DoublyLinkedListLinkImpl<RawDevice> { return B_BAD_VALUE; } - // find a free slot - fIndex = 0; - RawDevice* nextDevice = NULL; - MutexLocker locker(sDeviceListLock); - for (RawDeviceList::Iterator it = sDeviceList.GetIterator(); - (nextDevice = it.Next()) != NULL;) { - if (nextDevice->Index() > fIndex) - break; - fIndex = nextDevice->Index() + 1; - } - - sDeviceList.InsertBefore(nextDevice, this); - // construct our device path KPath path(kRawDeviceBaseName); char buffer[32]; - snprintf(buffer, sizeof(buffer), "%" B_PRId32 "/raw", fIndex); + snprintf(buffer, sizeof(buffer), "%" B_PRId32 "/raw", fID); status_t error = path.Append(buffer); if (error != B_OK) @@ -191,6 +212,17 @@ struct RawDevice : Device, DoublyLinkedListLinkImpl<RawDevice> { fDeviceName = path.DetachBuffer(); + // insert into device list + RawDevice* nextDevice = NULL; + MutexLocker locker(sDeviceListLock); + for (RawDeviceList::Iterator it = sDeviceList.GetIterator(); + (nextDevice = it.Next()) != NULL;) { + if (nextDevice->ID() > fID) + break; + } + + sDeviceList.InsertBefore(nextDevice, this); + return B_OK; } @@ -268,6 +300,15 @@ struct RawDevice : Device, DoublyLinkedListLinkImpl<RawDevice> { } } + void GetInfo(ram_disk_ioctl_info& _info) const + { + _info.id = fID; + _info.size = fDeviceSize; + memset(&_info.path, 0, sizeof(_info.path)); + if (fFilePath != NULL) + strlcpy(_info.path, fFilePath, sizeof(_info.path)); + } + status_t Flush() { static const size_t kPageCountPerIteration = 1024; @@ -726,7 +767,8 @@ private: } private: - int32 fIndex; + int32 fID; + bool fUnregistered; off_t fDeviceSize; char* fDeviceName; char* fFilePath; @@ -756,96 +798,35 @@ private: // #pragma mark - -static bool -parse_command_line(char* buffer, char**& _argv, int& _argc) +static int32 +allocate_raw_device_id() { - // Process the argument string. We split at whitespace, heeding quotes and - // escaped characters. The processed arguments are written to the given - // buffer, separated by single null chars. - char* start = buffer; - char* out = buffer; - bool pendingArgument = false; - int argc = 0; - while (*start != '\0') { - // ignore whitespace - if (isspace(*start)) { - if (pendingArgument) { - *out = '\0'; - out++; - argc++; - pendingArgument = false; - } - start++; - continue; - } - - pendingArgument = true; - - if (*start == '"' || *start == '\'') { - // quoted text -- continue until closing quote - char quote = *start; - start++; - while (*start != '\0' && *start != quote) { - if (*start == '\\' && quote == '"') { - start++; - if (*start == '\0') - break; - } - *out = *start; - start++; - out++; - } - - if (*start != '\0') - start++; - } else { - // unquoted text - if (*start == '\\') { - // escaped char - start++; - if (start == '\0') - break; - } - - *out = *start; - start++; - out++; + MutexLocker deviceListLocker(sDeviceListLock); + for (size_t i = 0; i < sizeof(sUsedRawDeviceIDs) * 8; i++) { + if ((sUsedRawDeviceIDs & ((uint64)1 << i)) == 0) { + sUsedRawDeviceIDs |= (uint64)1 << i; + return (int32)i; } } - if (pendingArgument) { - *out = '\0'; - argc++; - } + return -1; +} - // allocate argument vector - char** argv = new(std::nothrow) char*[argc + 1]; - if (argv == NULL) - return false; - // fill vector - start = buffer; - for (int i = 0; i < argc; i++) { - argv[i] = start; - start += strlen(start) + 1; - } - argv[argc] = NULL; - - _argv = argv; - _argc = argc; - return true; +static void +free_raw_device_id(int32 id) +{ + MutexLocker deviceListLocker(sDeviceListLock); + sUsedRawDeviceIDs &= ~((uint64)1 << id); } static RawDevice* -find_raw_device(const char* deviceName) +find_raw_device(int32 id) { - if (strncmp(deviceName, "/dev/", 5) == 0) - deviceName += 5; - for (RawDeviceList::Iterator it = sDeviceList.GetIterator(); RawDevice* device = it.Next();) { - if (strcmp(device->DeviceName(), deviceName) == 0) + if (device->ID() == id) return device; } @@ -853,6 +834,124 @@ find_raw_device(const char* deviceName) } +static status_t +ioctl_register(ControlDevice* controlDevice, ram_disk_ioctl_register* request) +{ + KPath path; + uint64 deviceSize = 0; + + if (request->path[0] != '\0') { + // check if the path is null-terminated + if (strnlen(request->path, sizeof(request->path)) + == sizeof(request->path)) { + return B_BAD_VALUE; + } + + // get a normalized file path + status_t error = path.SetTo(request->path, true); + if (error != B_OK) { + dprintf("ramdisk: register: Invalid path \"%s\": %s\n", + request->path, strerror(error)); + return B_BAD_VALUE; + } + + struct stat st; + if (lstat(path.Path(), &st) != 0) { + dprintf("ramdisk: register: Failed to stat \"%s\": %s\n", + path.Path(), strerror(errno)); + return errno; + } + + if (!S_ISREG(st.st_mode)) { + dprintf("ramdisk: register: \"%s\" is not a file!\n", path.Path()); + return B_BAD_VALUE; + } + + deviceSize = st.st_size; + } else { + deviceSize = request->size; + } + + return controlDevice->Register(path.Length() > 0 ? path.Path() : NULL, + deviceSize, request->id); +} + + +static status_t +ioctl_unregister(ControlDevice* controlDevice, + ram_disk_ioctl_unregister* request) +{ + // find the device in the list and unregister it + MutexLocker locker(sDeviceListLock); + RawDevice* device = find_raw_device(request->id); + if (device == NULL) + return B_ENTRY_NOT_FOUND; + + // mark unregistered before we unlock + if (device->IsUnregistered()) + return B_BUSY; + device->SetUnregistered(true); + locker.Unlock(); + + device_node* node = device->Node(); + status_t error = sDeviceManager->unpublish_device(node, + device->DeviceName()); + if (error != B_OK) { + dprintf("ramdisk: unregister: Failed to unpublish device \"%s\": %s\n", + device->DeviceName(), strerror(error)); + return error; + } + + error = sDeviceManager->unregister_node(node); + // Note: B_BUSY is OK. The node will removed as soon as possible. + if (error != B_OK && error != B_BUSY) { + dprintf("ramdisk: unregister: Failed to unregister node for device %" + B_PRId32 ": %s\n", request->id, strerror(error)); + return error; + } + + return B_OK; +} + + +static status_t +ioctl_info(RawDevice* device, ram_disk_ioctl_info* request) +{ + device->GetInfo(*request); + return B_OK; +} + + +template<typename DeviceType, typename Request> +static status_t +handle_ioctl(DeviceType* device, + status_t (*handler)(DeviceType*, Request*), void* buffer) +{ + // copy request to the kernel heap + if (buffer == NULL || !IS_USER_ADDRESS(buffer)) + return B_BAD_ADDRESS; + + Request* request = new(std::nothrow) Request; + if (request == NULL) + return B_NO_MEMORY; + ObjectDeleter<Request> requestDeleter(request); + + if (user_memcpy(request, buffer, sizeof(Request)) != B_OK) + return B_BAD_ADDRESS; + + // handle the ioctl + status_t error = handler(device, request); + if (error != B_OK) + return error; + + // copy the request back to userland + if (user_memcpy(buffer, request, sizeof(Request)) != B_OK) + return B_BAD_ADDRESS; + + return B_OK; +} + + // #pragma mark - driver @@ -890,6 +989,12 @@ ram_disk_driver_init_driver(device_node* node, void** _driverCookie) uint64 deviceSize; if (sDeviceManager->get_attr_uint64(node, kDeviceSizeItem, &deviceSize, false) == B_OK) { + int32 id = -1; + sDeviceManager->get_attr_uint32(node, kDeviceIDItem, (uint32*)&id, + false); + if (id < 0) + return B_ERROR; + const char* filePath = NULL; sDeviceManager->get_attr_string(node, kFilePathItem, &filePath, false); @@ -897,7 +1002,7 @@ ram_disk_driver_init_driver(device_node* node, void** _driverCookie) if (device == NULL) return B_NO_MEMORY; - status_t error = device->Init(filePath, deviceSize); + status_t error = device->Init(id, filePath, deviceSize); if (error != B_OK) { delete device; return error; @@ -920,6 +1025,8 @@ static void ram_disk_driver_uninit_driver(void* driverCookie) { Device* device = (Device*)driverCookie; + if (RawDevice* rawDevice = dynamic_cast<RawDevice*>(device)) + free_raw_device_id(rawDevice->ID()); delete device; } @@ -976,185 +1083,15 @@ static status_t ram_disk_control_device_read(void* cookie, off_t position, void* buffer, size_t* _length) { - *_length = 0; - return B_OK; + return B_BAD_VALUE; } - static status_t ram_disk_control_device_write(void* cookie, off_t position, const void* data, size_t* _length) { - ControlDevice* device = (ControlDevice*)cookie; - - if (position != 0) - return B_BAD_VALUE; - - // copy data to stack buffer - char* buffer = (char*)malloc(*_length + 1); - if (buffer == NULL) - return B_NO_MEMORY; - MemoryDeleter bufferDeleter(buffer); - - if (IS_USER_ADDRESS(data)) { - if (user_memcpy(buffer, data, *_length) != B_OK) - return B_BAD_ADDRESS; - } else - memcpy(buffer, data, *_length); - - buffer[*_length] = '\0'; - - // parse arguments - char** argv; - int argc; - if (!parse_command_line(buffer, argv, argc)) - return B_NO_MEMORY; - ArrayDeleter<char*> argvDeleter(argv); - - if (argc == 0) { - dprintf("\"help\" for usage!\n"); - return B_BAD_VALUE; - } - - // execute command - if (strcmp(argv[0], "help") == 0) { - // help - dprintf("register (<path> | -s <size>)\n"); - dprintf(" Registers a new ram disk device backed by file <path> or\n" - " an unbacked ram disk device with size <size>.\n"); - dprintf("unregister <device>\n"); - dprintf(" Unregisters <device>.\n"); - dprintf("flush <device>\n"); - dprintf(" Writes <device> changes back to the file associated with\n" - " it, if any.\n"); - } else if (strcmp(argv[0], "register") == 0) { - // register - if (argc < 2 || argc > 3 || (argc == 3 && strcmp(argv[1], "-s") != 0)) { - dprintf("Usage: register (<path> | -s <size>)\n"); - return B_BAD_VALUE; - } - - KPath path; - uint64 deviceSize = 0; - - if (argc == 2) { - // get a normalized file path - status_t error = path.SetTo(argv[1], true); - if (error != B_OK) { - dprintf("Invalid path \"%s\": %s\n", argv[1], strerror(error)); - return B_BAD_VALUE; - } - - struct stat st; - if (lstat(path.Path(), &st) != 0) { - dprintf("Failed to stat \"%s\": %s\n", path.Path(), - strerror(errno)); - return errno; - } - - if (!S_ISREG(st.st_mode)) { - dprintf("\"%s\" is not a file!\n", path.Path()); - return B_BAD_VALUE; - } - - deviceSize = st.st_size; - } else { - // parse size argument - const char* sizeString = argv[2]; - char* end; - deviceSize = strtoll(sizeString, &end, 0); - if (end == sizeString) { - dprintf("Invalid size argument: \"%s\"\n", sizeString); - return B_BAD_VALUE; - } - - switch (*end) { - case 'g': - deviceSize *= 1024; - case 'm': - deviceSize *= 1024; - case 'k': - deviceSize *= 1024; - break; - } - } - - return device->Register(path.Length() > 0 ? path.Path() : NULL, - deviceSize); - } else if (strcmp(argv[0], "unregister") == 0) { - // unregister - if (argc != 2) { - dprintf("Usage: unregister <device>\n"); - return B_BAD_VALUE; - } - - const char* deviceName = argv[1]; - - // find the device in the list and unregister it - MutexLocker locker(sDeviceListLock); - if (RawDevice* device = find_raw_device(deviceName)) { - // TODO: Race condition: We should mark the device as going to - // be unregistered, so no one else can try the same after we - // unlock! - locker.Unlock(); -// TODO: The following doesn't work! unpublish_device(), as per implementation -// (partially commented out) and unregister_node() returns B_BUSY. - status_t error = sDeviceManager->unpublish_device( - device->Node(), device->DeviceName()); - if (error != B_OK) { - dprintf("Failed to unpublish device \"%s\": %s\n", - deviceName, strerror(error)); - return error; - } - - error = sDeviceManager->unregister_node(device->Node()); - if (error != B_OK) { - dprintf("Failed to unregister node \"%s\": %s\n", - deviceName, strerror(error)); - return error; - } - - return B_OK; - } - - dprintf("Device \"%s\" not found!\n", deviceName); - return B_BAD_VALUE; - } else if (strcmp(argv[0], "flush") == 0) { - // flush - if (argc != 2) { - dprintf("Usage: unregister <device>\n"); - return B_BAD_VALUE; - } - - const char* deviceName = argv[1]; - - // find the device in the list and flush it - MutexLocker locker(sDeviceListLock); - RawDevice* device = find_raw_device(deviceName); - if (device == NULL) { - dprintf("Device \"%s\" not found!\n", deviceName); - return B_BAD_VALUE; - } - - // TODO: Race condition: Once we unlock someone could unregister the - // device. We should probably open the device by path, and use a - // special ioctl. - locker.Unlock(); - - status_t error = device->Flush(); - if (error != B_OK) { - dprintf("Failed to flush device: %s\n", strerror(error)); - return error; - } - - return B_OK; - } else { - dprintf("Invalid command \"%s\"!\n", argv[0]); - return B_BAD_VALUE; - } - - return B_OK; + return B_BAD_VALUE; } @@ -1162,6 +1099,16 @@ static status_t ram_disk_control_device_control(void* cookie, uint32 op, void* buffer, size_t length) { + ControlDevice* device = (ControlDevice*)cookie; + + switch (op) { + case RAM_DISK_IOCTL_REGISTER: + return handle_ioctl(device, &ioctl_register, buffer); + + case RAM_DISK_IOCTL_UNREGISTER: + return handle_ioctl(device, &ioctl_unregister, buffer); + } + return B_BAD_VALUE; } @@ -1347,6 +1294,21 @@ ram_disk_raw_device_control(void* _cookie, uint32 op, void* buffer, case B_SET_INTERRUPTABLE_IO: case B_FLUSH_DRIVE_CACHE: return B_OK; + + case RAM_DISK_IOCTL_FLUSH: + { + status_t error = device->Flush(); + if (error != B_OK) { + dprintf("ramdisk: flush: Failed to flush device: %s\n", + strerror(error)); + return error; + } + + return B_OK; + } + + case RAM_DISK_IOCTL_INFO: + return handle_ioctl(device, &ioctl_info, buffer); } return B_BAD_VALUE; diff --git a/src/bin/Jamfile b/src/bin/Jamfile index 5cb2794fa9..67c51e695a 100644 --- a/src/bin/Jamfile +++ b/src/bin/Jamfile @@ -126,6 +126,11 @@ StdBinCommands urlwrapper.cpp : be $(TARGET_LIBSUPC++) : $(haiku-utils_rsrc) ; +# standard commands that need libbe.so, libsupc++.so, and libshared.a +StdBinCommands + ramdisk.cpp + : libshared.a be $(TARGET_LIBSUPC++) : $(haiku-utils_rsrc) ; + # commands that need libbe.so and the stub catalog StdBinCommands clockconfig.cpp diff --git a/src/bin/ramdisk.cpp b/src/bin/ramdisk.cpp new file mode 100644 index 0000000000..b6070ea9f7 --- /dev/null +++ b/src/bin/ramdisk.cpp @@ -0,0 +1,477 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <Entry.h> +#include <Path.h> +#include <String.h> + +#include <AutoDeleter.h> +#include <TextTable.h> + +#include <file_systems/ram_disk/ram_disk.h> + + +extern const char* __progname; +static const char* kProgramName = __progname; + +static const char* const kUsage = + "Usage: %s <command> [ <options> ]\n" + "Controls RAM disk devices.\n" + "\n" + "Commands:\n" + " create (-s <size> | <path>)\n" + " Creates a new RAM disk.\n" + " delete <id>\n" + " Deletes an existing RAM disk.\n" + " flush <id>\n" + " Writes modified data of an existing RAM disk back to its file.\n" + " help\n" + " Print this usage info.\n" + " list\n" + " List all RAM disks.\n" +; + +static const char* const kCreateUsage = + "Usage: %s %s (-s <size> | <path>)\n" + "Creates a new RAM disk device. If the <size> argument is specified, a\n" + "new zeroed RAM disk with that size (in bytes, suffixes 'k', 'm', 'g' are\n" + "interpreted as KiB, MiB, GiB) is registered.\n" + "Alternatively a file path can be specified. In that case the RAM disk \n" + "data are initially read from that file and at any later point the\n" + "modified RAM disk data can be written back to the same file upon request\n" + "(via the \"flush\" command). The size of the RAM disk is implied by that\n" + "of the file.\n" +; + +static const char* const kDeleteUsage = + "Usage: %s %s <id>\n" + "Deletes the existing RAM disk with ID <id>. All modified data will be\n" + "lost.\n" +; + +static const char* const kFlushUsage = + "Usage: %s %s <id>\n" + "Writes all modified data of the RAM disk with ID <id> back to the file\n" + "specified when the RAM disk was created. Fails, if the RAM disk had been\n" + "created without an associated file.\n" +; + +static const char* const kListUsage = + "Usage: %s %s\n" + "Lists all existing RAM disks.\n" +; + +static const char* const kRamDiskControlDevicePath + = "/dev/" RAM_DISK_CONTROL_DEVICE_NAME; +static const char* const kRamDiskRawDeviceBasePath + = "/dev/" RAM_DISK_RAW_DEVICE_BASE_NAME; + +static const char* sCommandName = NULL; +static const char* sCommandUsage = NULL; + + +static void +print_usage_and_exit(bool error) +{ + if (sCommandUsage != NULL) { + fprintf(error ? stderr : stdout, sCommandUsage, kProgramName, + sCommandName); + } else + fprintf(error ? stderr : stdout, kUsage, kProgramName); + exit(error ? 1 : 0); +} + + +static status_t +execute_control_device_ioctl(int operation, void* request) +{ + // open the ram disk control device + int fd = open(kRamDiskControlDevicePath, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Error: Failed to open RAM disk control device \"%s\": " + "%s\n", kRamDiskControlDevicePath, strerror(errno)); + return errno; + } + FileDescriptorCloser fdCloser(fd); + + // issue the request + if (ioctl(fd, operation, request) < 0) + return errno; + + return B_OK; +} + + +static int +command_register(int argc, const char* const* argv) +{ + sCommandUsage = kCreateUsage; + + int64 deviceSize = -1; + + while (true) { + static struct option sLongOptions[] = { + { "size", required_argument, 0, 's' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + opterr = 0; // don't print errors + int c = getopt_long(argc, (char**)argv, "+s:h", sLongOptions, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage_and_exit(false); + break; + + case 's': + { + const char* sizeString = optarg; + char* end; + deviceSize = strtoll(sizeString, &end, 0); + if (end != sizeString && deviceSize > 0) { + int64 originalDeviceSize = deviceSize; + switch (*end) { + case 'g': + deviceSize *= 1024; + case 'm': + deviceSize *= 1024; + case 'k': + deviceSize *= 1024; + end++; + break; + case '\0': + break; + default: + deviceSize = -1; + break; + } + + if (deviceSize > 0 && originalDeviceSize > deviceSize) + deviceSize = -1; + } + + if (deviceSize <= 0) { + fprintf(stderr, "Error: Invalid size argument: \"%s\"\n", + sizeString); + return 1; + } + + // check maximum size + system_info info; + get_system_info(&info); + if (deviceSize / B_PAGE_SIZE > (int64)info.max_pages * 2 / 3) { + fprintf(stderr, "Error: Given RAM disk size too large.\n"); + return 1; + } + + break; + } + + default: + print_usage_and_exit(true); + break; + } + } + + // The remaining optional argument is the file path. It may only be + // specified, if no size has been specified. + const char* path = optind < argc ? argv[optind++] : NULL; + if (optind < argc || (deviceSize >= 0) == (path != NULL)) + print_usage_and_exit(true); + + // prepare the request + ram_disk_ioctl_register request; + request.size = (uint64)deviceSize; + request.path[0] = '\0'; + request.id = -1; + + if (path != NULL) { + // verify the path + BEntry entry; + status_t error = entry.SetTo(path, true); + if (error == B_OK && !entry.Exists()) + error = B_ENTRY_NOT_FOUND; + if (error != B_OK) { + fprintf(stderr, "Error: Failed to resolve path \"%s\": %s\n", + path, strerror(error)); + return 1; + } + + if (!entry.IsFile()) { + fprintf(stderr, "Error: \"%s\" is not a file.\n", path); + return 1; + } + + BPath normalizedPath; + error = entry.GetPath(&normalizedPath); + if (error != B_OK) { + fprintf(stderr, "Error: Failed to normalize path \"%s\": %s\n", + path, strerror(error)); + return 1; + } + + if (strlcpy(request.path, normalizedPath.Path(), sizeof(request.path)) + >= sizeof(request.path)) { + fprintf(stderr, "Error: Normalized path too long.\n"); + return 1; + } + } + + status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_REGISTER, + &request); + if (error != B_OK) { + fprintf(stderr, "Error: Failed to create RAM disk device: %s\n", + strerror(error)); + return 1; + } + + printf("RAM disk device created as \"%s/%" B_PRId32 "/raw\"\n", + kRamDiskRawDeviceBasePath, request.id); + return 0; +} + + +static int +command_unregister(int argc, const char* const* argv) +{ + sCommandUsage = kDeleteUsage; + + while (true) { + static struct option sLongOptions[] = { + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + opterr = 0; // don't print errors + int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage_and_exit(false); + break; + + default: + print_usage_and_exit(true); + break; + } + } + + // The remaining argument is the device ID. + if (optind + 1 != argc) + print_usage_and_exit(true); + + const char* idString = argv[optind++]; + char* end; + long long id = strtol(idString, &end, 0); + if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) { + fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString); + return 1; + } + + // check whether the raw device for that ID exists + BString path; + path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString); + struct stat st; + if (lstat(path, &st) != 0) { + fprintf(stderr, "Error: No RAM disk with ID %s.\n", idString); + return 1; + } + + // issue the request + ram_disk_ioctl_unregister request; + request.id = (int32)id; + + status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_UNREGISTER, + &request); + if (error != B_OK) { + fprintf(stderr, "Error: Failed to delete RAM disk device: %s\n", + strerror(error)); + return 1; + } + + return 0; +} + + +static int +command_flush(int argc, const char* const* argv) +{ + sCommandUsage = kFlushUsage; + + while (true) { + static struct option sLongOptions[] = { + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + opterr = 0; // don't print errors + int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage_and_exit(false); + break; + + default: + print_usage_and_exit(true); + break; + } + } + + // The remaining argument is the device ID. + if (optind + 1 != argc) + print_usage_and_exit(true); + + const char* idString = argv[optind++]; + char* end; + long long id = strtol(idString, &end, 0); + if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) { + fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString); + return 1; + } + + // open the raw device + BString path; + path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString); + int fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Error: Failed to open RAM disk device \"%s\"\n", + path.String()); + return 1; + } + FileDescriptorCloser fdCloser(fd); + + // issue the request + if (ioctl(fd, RAM_DISK_IOCTL_FLUSH, NULL) < 0) { + fprintf(stderr, "Error: Failed to flush RAM disk device: %s\n", + strerror(errno)); + return 1; + } + + return 0; +} + + +static int +command_list(int argc, const char* const* argv) +{ + sCommandUsage = kListUsage; + + while (true) { + static struct option sLongOptions[] = { + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + opterr = 0; // don't print errors + int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage_and_exit(false); + break; + + default: + print_usage_and_exit(true); + break; + } + } + + // There shouldn't be any remaining arguments. + if (optind != argc) + print_usage_and_exit(true); + + // iterate through the RAM disk device directory and search for raw devices + DIR* dir = opendir(kRamDiskRawDeviceBasePath); + if (dir == NULL) { + fprintf(stderr, "Error: Failed to open RAM disk device directory: %s\n", + strerror(errno)); + return 1; + } + CObjectDeleter<DIR, int> dirCloser(dir, &closedir); + + TextTable table; + table.AddColumn("ID", B_ALIGN_RIGHT); + table.AddColumn("Size", B_ALIGN_RIGHT); + table.AddColumn("Associated file"); + + while (dirent* entry = readdir(dir)) { + // check, if the entry name could be an ID + const char* idString = entry->d_name; + char* end; + long long id = strtol(idString, &end, 0); + if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) + continue; + + // open the raw device + BString path; + path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString); + int fd = open(path, O_RDONLY); + if (fd < 0) + continue; + FileDescriptorCloser fdCloser(fd); + + // issue the request + ram_disk_ioctl_info request; + if (ioctl(fd, RAM_DISK_IOCTL_INFO, &request) < 0) + continue; + + int32 rowIndex = table.CountRows(); + table.SetTextAt(rowIndex, 0, BString() << request.id); + table.SetTextAt(rowIndex, 1, BString() << request.size); + table.SetTextAt(rowIndex, 2, request.path); + } + + if (table.CountRows() > 0) + table.Print(INT32_MAX); + else + printf("No RAM disks.\n"); + + return 0; +} + + +int +main(int argc, const char* const* argv) +{ + if (argc < 2) + print_usage_and_exit(true); + + if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 + || strcmp(argv[1], "-h") == 0) { + print_usage_and_exit(false); + } + + sCommandName = argv[1]; + + if (strcmp(sCommandName, "create") == 0) + return command_register(argc - 1, argv + 1); + if (strcmp(sCommandName, "delete") == 0) + return command_unregister(argc - 1, argv + 1); + if (strcmp(sCommandName, "flush") == 0) + return command_flush(argc - 1, argv + 1); + if (strcmp(sCommandName, "list") == 0) + return command_list(argc - 1, argv + 1); + + print_usage_and_exit(true); +}