1 /* 2 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <dirent.h> 8 #include <errno.h> 9 #include <fcntl.h> 10 #include <getopt.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 15 #include <Entry.h> 16 #include <Path.h> 17 #include <String.h> 18 19 #include <AutoDeleter.h> 20 #include <TextTable.h> 21 22 #include <file_systems/ram_disk/ram_disk.h> 23 24 25 extern const char* __progname; 26 static const char* kProgramName = __progname; 27 28 static const char* const kUsage = 29 "Usage: %s <command> [ <options> ]\n" 30 "Controls RAM disk devices.\n" 31 "\n" 32 "Commands:\n" 33 " create (-s <size> | <path>)\n" 34 " Creates a new RAM disk.\n" 35 " delete <id>\n" 36 " Deletes an existing RAM disk.\n" 37 " flush <id>\n" 38 " Writes modified data of an existing RAM disk back to its file.\n" 39 " help\n" 40 " Print this usage info.\n" 41 " list\n" 42 " List all RAM disks.\n" 43 ; 44 45 static const char* const kCreateUsage = 46 "Usage: %s %s (-s <size> | <path>)\n" 47 "Creates a new RAM disk device. If the <size> argument is specified, a\n" 48 "new zeroed RAM disk with that size (in bytes, suffixes 'k', 'm', 'g' are\n" 49 "interpreted as KiB, MiB, GiB) is registered.\n" 50 "Alternatively a file path can be specified. In that case the RAM disk \n" 51 "data are initially read from that file and at any later point the\n" 52 "modified RAM disk data can be written back to the same file upon request\n" 53 "(via the \"flush\" command). The size of the RAM disk is implied by that\n" 54 "of the file.\n" 55 ; 56 57 static const char* const kDeleteUsage = 58 "Usage: %s %s <id>\n" 59 "Deletes the existing RAM disk with ID <id>. All modified data will be\n" 60 "lost.\n" 61 ; 62 63 static const char* const kFlushUsage = 64 "Usage: %s %s <id>\n" 65 "Writes all modified data of the RAM disk with ID <id> back to the file\n" 66 "specified when the RAM disk was created. Fails, if the RAM disk had been\n" 67 "created without an associated file.\n" 68 ; 69 70 static const char* const kListUsage = 71 "Usage: %s %s\n" 72 "Lists all existing RAM disks.\n" 73 ; 74 75 static const char* const kRamDiskControlDevicePath 76 = "/dev/" RAM_DISK_CONTROL_DEVICE_NAME; 77 static const char* const kRamDiskRawDeviceBasePath 78 = "/dev/" RAM_DISK_RAW_DEVICE_BASE_NAME; 79 80 static const char* sCommandName = NULL; 81 static const char* sCommandUsage = NULL; 82 83 84 static void 85 print_usage_and_exit(bool error) 86 { 87 if (sCommandUsage != NULL) { 88 fprintf(error ? stderr : stdout, sCommandUsage, kProgramName, 89 sCommandName); 90 } else 91 fprintf(error ? stderr : stdout, kUsage, kProgramName); 92 exit(error ? 1 : 0); 93 } 94 95 96 static status_t 97 execute_control_device_ioctl(int operation, void* request) 98 { 99 // open the ram disk control device 100 int fd = open(kRamDiskControlDevicePath, O_RDONLY); 101 if (fd < 0) { 102 fprintf(stderr, "Error: Failed to open RAM disk control device \"%s\": " 103 "%s\n", kRamDiskControlDevicePath, strerror(errno)); 104 return errno; 105 } 106 FileDescriptorCloser fdCloser(fd); 107 108 // issue the request 109 if (ioctl(fd, operation, request) < 0) 110 return errno; 111 112 return B_OK; 113 } 114 115 116 static int 117 command_register(int argc, const char* const* argv) 118 { 119 sCommandUsage = kCreateUsage; 120 121 int64 deviceSize = -1; 122 123 while (true) { 124 static struct option sLongOptions[] = { 125 { "size", required_argument, 0, 's' }, 126 { "help", no_argument, 0, 'h' }, 127 { 0, 0, 0, 0 } 128 }; 129 130 opterr = 0; // don't print errors 131 int c = getopt_long(argc, (char**)argv, "+s:h", sLongOptions, NULL); 132 if (c == -1) 133 break; 134 135 switch (c) { 136 case 'h': 137 print_usage_and_exit(false); 138 break; 139 140 case 's': 141 { 142 const char* sizeString = optarg; 143 char* end; 144 deviceSize = strtoll(sizeString, &end, 0); 145 if (end != sizeString && deviceSize > 0) { 146 int64 originalDeviceSize = deviceSize; 147 switch (*end) { 148 case 'g': 149 deviceSize *= 1024; 150 case 'm': 151 deviceSize *= 1024; 152 case 'k': 153 deviceSize *= 1024; 154 end++; 155 break; 156 case '\0': 157 break; 158 default: 159 deviceSize = -1; 160 break; 161 } 162 163 if (deviceSize > 0 && originalDeviceSize > deviceSize) 164 deviceSize = -1; 165 } 166 167 if (deviceSize <= 0) { 168 fprintf(stderr, "Error: Invalid size argument: \"%s\"\n", 169 sizeString); 170 return 1; 171 } 172 173 // check maximum size 174 system_info info; 175 get_system_info(&info); 176 if (deviceSize / B_PAGE_SIZE > (int64)info.max_pages * 2 / 3) { 177 fprintf(stderr, "Error: Given RAM disk size too large.\n"); 178 return 1; 179 } 180 181 break; 182 } 183 184 default: 185 print_usage_and_exit(true); 186 break; 187 } 188 } 189 190 // The remaining optional argument is the file path. It may only be 191 // specified, if no size has been specified. 192 const char* path = optind < argc ? argv[optind++] : NULL; 193 if (optind < argc || (deviceSize >= 0) == (path != NULL)) 194 print_usage_and_exit(true); 195 196 // prepare the request 197 ram_disk_ioctl_register request; 198 request.size = (uint64)deviceSize; 199 request.path[0] = '\0'; 200 request.id = -1; 201 202 if (path != NULL) { 203 // verify the path 204 BEntry entry; 205 status_t error = entry.SetTo(path, true); 206 if (error == B_OK && !entry.Exists()) 207 error = B_ENTRY_NOT_FOUND; 208 if (error != B_OK) { 209 fprintf(stderr, "Error: Failed to resolve path \"%s\": %s\n", 210 path, strerror(error)); 211 return 1; 212 } 213 214 if (!entry.IsFile()) { 215 fprintf(stderr, "Error: \"%s\" is not a file.\n", path); 216 return 1; 217 } 218 219 BPath normalizedPath; 220 error = entry.GetPath(&normalizedPath); 221 if (error != B_OK) { 222 fprintf(stderr, "Error: Failed to normalize path \"%s\": %s\n", 223 path, strerror(error)); 224 return 1; 225 } 226 227 if (strlcpy(request.path, normalizedPath.Path(), sizeof(request.path)) 228 >= sizeof(request.path)) { 229 fprintf(stderr, "Error: Normalized path too long.\n"); 230 return 1; 231 } 232 } 233 234 status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_REGISTER, 235 &request); 236 if (error != B_OK) { 237 fprintf(stderr, "Error: Failed to create RAM disk device: %s\n", 238 strerror(error)); 239 return 1; 240 } 241 242 printf("RAM disk device created as \"%s/%" B_PRId32 "/raw\"\n", 243 kRamDiskRawDeviceBasePath, request.id); 244 return 0; 245 } 246 247 248 static int 249 command_unregister(int argc, const char* const* argv) 250 { 251 sCommandUsage = kDeleteUsage; 252 253 while (true) { 254 static struct option sLongOptions[] = { 255 { "help", no_argument, 0, 'h' }, 256 { 0, 0, 0, 0 } 257 }; 258 259 opterr = 0; // don't print errors 260 int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL); 261 if (c == -1) 262 break; 263 264 switch (c) { 265 case 'h': 266 print_usage_and_exit(false); 267 break; 268 269 default: 270 print_usage_and_exit(true); 271 break; 272 } 273 } 274 275 // The remaining argument is the device ID. 276 if (optind + 1 != argc) 277 print_usage_and_exit(true); 278 279 const char* idString = argv[optind++]; 280 char* end; 281 long long id = strtol(idString, &end, 0); 282 if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) { 283 fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString); 284 return 1; 285 } 286 287 // check whether the raw device for that ID exists 288 BString path; 289 path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString); 290 struct stat st; 291 if (lstat(path, &st) != 0) { 292 fprintf(stderr, "Error: No RAM disk with ID %s.\n", idString); 293 return 1; 294 } 295 296 // issue the request 297 ram_disk_ioctl_unregister request; 298 request.id = (int32)id; 299 300 status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_UNREGISTER, 301 &request); 302 if (error != B_OK) { 303 fprintf(stderr, "Error: Failed to delete RAM disk device: %s\n", 304 strerror(error)); 305 return 1; 306 } 307 308 return 0; 309 } 310 311 312 static int 313 command_flush(int argc, const char* const* argv) 314 { 315 sCommandUsage = kFlushUsage; 316 317 while (true) { 318 static struct option sLongOptions[] = { 319 { "help", no_argument, 0, 'h' }, 320 { 0, 0, 0, 0 } 321 }; 322 323 opterr = 0; // don't print errors 324 int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL); 325 if (c == -1) 326 break; 327 328 switch (c) { 329 case 'h': 330 print_usage_and_exit(false); 331 break; 332 333 default: 334 print_usage_and_exit(true); 335 break; 336 } 337 } 338 339 // The remaining argument is the device ID. 340 if (optind + 1 != argc) 341 print_usage_and_exit(true); 342 343 const char* idString = argv[optind++]; 344 char* end; 345 long long id = strtol(idString, &end, 0); 346 if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) { 347 fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString); 348 return 1; 349 } 350 351 // open the raw device 352 BString path; 353 path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString); 354 int fd = open(path, O_RDONLY); 355 if (fd < 0) { 356 fprintf(stderr, "Error: Failed to open RAM disk device \"%s\"\n", 357 path.String()); 358 return 1; 359 } 360 FileDescriptorCloser fdCloser(fd); 361 362 // issue the request 363 if (ioctl(fd, RAM_DISK_IOCTL_FLUSH, NULL) < 0) { 364 fprintf(stderr, "Error: Failed to flush RAM disk device: %s\n", 365 strerror(errno)); 366 return 1; 367 } 368 369 return 0; 370 } 371 372 373 static int 374 command_list(int argc, const char* const* argv) 375 { 376 sCommandUsage = kListUsage; 377 378 while (true) { 379 static struct option sLongOptions[] = { 380 { "help", no_argument, 0, 'h' }, 381 { 0, 0, 0, 0 } 382 }; 383 384 opterr = 0; // don't print errors 385 int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL); 386 if (c == -1) 387 break; 388 389 switch (c) { 390 case 'h': 391 print_usage_and_exit(false); 392 break; 393 394 default: 395 print_usage_and_exit(true); 396 break; 397 } 398 } 399 400 // There shouldn't be any remaining arguments. 401 if (optind != argc) 402 print_usage_and_exit(true); 403 404 // iterate through the RAM disk device directory and search for raw devices 405 DIR* dir = opendir(kRamDiskRawDeviceBasePath); 406 if (dir == NULL) { 407 fprintf(stderr, "Error: Failed to open RAM disk device directory: %s\n", 408 strerror(errno)); 409 return 1; 410 } 411 CObjectDeleter<DIR, int> dirCloser(dir, &closedir); 412 413 TextTable table; 414 table.AddColumn("ID", B_ALIGN_RIGHT); 415 table.AddColumn("Size", B_ALIGN_RIGHT); 416 table.AddColumn("Associated file"); 417 418 while (dirent* entry = readdir(dir)) { 419 // check, if the entry name could be an ID 420 const char* idString = entry->d_name; 421 char* end; 422 long long id = strtol(idString, &end, 0); 423 if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) 424 continue; 425 426 // open the raw device 427 BString path; 428 path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString); 429 int fd = open(path, O_RDONLY); 430 if (fd < 0) 431 continue; 432 FileDescriptorCloser fdCloser(fd); 433 434 // issue the request 435 ram_disk_ioctl_info request; 436 if (ioctl(fd, RAM_DISK_IOCTL_INFO, &request) < 0) 437 continue; 438 439 int32 rowIndex = table.CountRows(); 440 table.SetTextAt(rowIndex, 0, BString() << request.id); 441 table.SetTextAt(rowIndex, 1, BString() << request.size); 442 table.SetTextAt(rowIndex, 2, request.path); 443 } 444 445 if (table.CountRows() > 0) 446 table.Print(INT32_MAX); 447 else 448 printf("No RAM disks.\n"); 449 450 return 0; 451 } 452 453 454 int 455 main(int argc, const char* const* argv) 456 { 457 if (argc < 2) 458 print_usage_and_exit(true); 459 460 if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0 461 || strcmp(argv[1], "-h") == 0) { 462 print_usage_and_exit(false); 463 } 464 465 sCommandName = argv[1]; 466 467 if (strcmp(sCommandName, "create") == 0) 468 return command_register(argc - 1, argv + 1); 469 if (strcmp(sCommandName, "delete") == 0) 470 return command_unregister(argc - 1, argv + 1); 471 if (strcmp(sCommandName, "flush") == 0) 472 return command_flush(argc - 1, argv + 1); 473 if (strcmp(sCommandName, "list") == 0) 474 return command_list(argc - 1, argv + 1); 475 476 print_usage_and_exit(true); 477 } 478