fs.c (23696B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "fs.h" 21#include "download.h" 22#include "upload.h" 23 24#include <guacamole/client.h> 25#include <guacamole/mem.h> 26#include <guacamole/object.h> 27#include <guacamole/pool.h> 28#include <guacamole/protocol.h> 29#include <guacamole/socket.h> 30#include <guacamole/string.h> 31#include <guacamole/user.h> 32#include <winpr/file.h> 33#include <winpr/nt.h> 34 35#include <dirent.h> 36#include <errno.h> 37#include <fcntl.h> 38#include <fnmatch.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <sys/stat.h> 43#include <sys/statvfs.h> 44#include <unistd.h> 45 46guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path, 47 int create_drive_path, int disable_download, int disable_upload) { 48 49 /* Create drive path if it does not exist */ 50 if (create_drive_path) { 51 guac_client_log(client, GUAC_LOG_DEBUG, 52 "%s: Creating directory \"%s\" if necessary.", 53 __func__, drive_path); 54 55 /* Log error if directory creation fails */ 56 if (mkdir(drive_path, S_IRWXU) && errno != EEXIST) { 57 guac_client_log(client, GUAC_LOG_ERROR, 58 "Unable to create directory \"%s\": %s", 59 drive_path, strerror(errno)); 60 } 61 } 62 63 guac_rdp_fs* fs = guac_mem_alloc(sizeof(guac_rdp_fs)); 64 65 fs->client = client; 66 fs->drive_path = guac_strdup(drive_path); 67 fs->file_id_pool = guac_pool_alloc(0); 68 fs->open_files = 0; 69 fs->disable_download = disable_download; 70 fs->disable_upload = disable_upload; 71 72 return fs; 73 74} 75 76void guac_rdp_fs_free(guac_rdp_fs* fs) { 77 guac_pool_free(fs->file_id_pool); 78 guac_mem_free(fs->drive_path); 79 guac_mem_free(fs); 80} 81 82guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user) { 83 84 /* Init filesystem */ 85 guac_object* fs_object = guac_user_alloc_object(user); 86 fs_object->get_handler = guac_rdp_download_get_handler; 87 88 /* Assign handler only if uploads are not disabled. */ 89 if (!fs->disable_upload) 90 fs_object->put_handler = guac_rdp_upload_put_handler; 91 92 fs_object->data = fs; 93 94 /* Send filesystem to user */ 95 guac_protocol_send_filesystem(user->socket, fs_object, "Shared Drive"); 96 guac_socket_flush(user->socket); 97 98 return fs_object; 99 100} 101 102void* guac_rdp_fs_expose(guac_user* user, void* data) { 103 104 guac_rdp_fs* fs = (guac_rdp_fs*) data; 105 106 /* No need to expose if there is no filesystem or the user has left */ 107 if (user == NULL || fs == NULL) 108 return NULL; 109 110 /* Allocate and expose filesystem object for user */ 111 return guac_rdp_fs_alloc_object(fs, user); 112 113} 114 115/** 116 * Translates an absolute Windows path to an absolute path which is within the 117 * "drive path" specified in the connection settings. No checking is performed 118 * on the path provided, which is assumed to have already been normalized and 119 * validated as absolute. 120 * 121 * @param fs 122 * The filesystem containing the file whose path is being translated. 123 * 124 * @param virtual_path 125 * The absolute path to the file on the simulated filesystem, relative to 126 * the simulated filesystem root. 127 * 128 * @param real_path 129 * The buffer in which to store the absolute path to the real file on the 130 * local filesystem. 131 */ 132static void __guac_rdp_fs_translate_path(guac_rdp_fs* fs, 133 const char* virtual_path, char* real_path) { 134 135 /* Get drive path */ 136 char* drive_path = fs->drive_path; 137 138 int i; 139 140 /* Start with path from settings */ 141 for (i=0; i<GUAC_RDP_FS_MAX_PATH-1; i++) { 142 143 /* Break on end-of-string */ 144 char c = *(drive_path++); 145 if (c == 0) 146 break; 147 148 /* Copy character */ 149 *(real_path++) = c; 150 151 } 152 153 /* Translate path */ 154 for (; i<GUAC_RDP_FS_MAX_PATH-1; i++) { 155 156 /* Stop at end of string */ 157 char c = *(virtual_path++); 158 if (c == 0) 159 break; 160 161 /* Translate backslashes to forward slashes */ 162 if (c == '\\') 163 c = '/'; 164 165 /* Store in real path buffer */ 166 *(real_path++)= c; 167 168 } 169 170 /* Null terminator */ 171 *real_path = 0; 172 173} 174 175int guac_rdp_fs_get_errorcode(int err) { 176 177 /* Translate errno codes to GUAC_RDP_FS codes */ 178 if (err == ENFILE) return GUAC_RDP_FS_ENFILE; 179 if (err == ENOENT) return GUAC_RDP_FS_ENOENT; 180 if (err == ENOTDIR) return GUAC_RDP_FS_ENOTDIR; 181 if (err == ENOSPC) return GUAC_RDP_FS_ENOSPC; 182 if (err == EISDIR) return GUAC_RDP_FS_EISDIR; 183 if (err == EACCES) return GUAC_RDP_FS_EACCES; 184 if (err == EEXIST) return GUAC_RDP_FS_EEXIST; 185 if (err == EINVAL) return GUAC_RDP_FS_EINVAL; 186 if (err == ENOSYS) return GUAC_RDP_FS_ENOSYS; 187 if (err == ENOTSUP) return GUAC_RDP_FS_ENOTSUP; 188 189 /* Default to invalid parameter */ 190 return GUAC_RDP_FS_EINVAL; 191 192} 193 194int guac_rdp_fs_get_status(int err) { 195 196 /* Translate GUAC_RDP_FS error code to RDPDR status code */ 197 if (err == GUAC_RDP_FS_ENFILE) return STATUS_NO_MORE_FILES; 198 if (err == GUAC_RDP_FS_ENOENT) return STATUS_NO_SUCH_FILE; 199 if (err == GUAC_RDP_FS_ENOTDIR) return STATUS_NOT_A_DIRECTORY; 200 if (err == GUAC_RDP_FS_ENOSPC) return STATUS_DISK_FULL; 201 if (err == GUAC_RDP_FS_EISDIR) return STATUS_FILE_IS_A_DIRECTORY; 202 if (err == GUAC_RDP_FS_EACCES) return STATUS_ACCESS_DENIED; 203 if (err == GUAC_RDP_FS_EEXIST) return STATUS_OBJECT_NAME_COLLISION; 204 if (err == GUAC_RDP_FS_EINVAL) return STATUS_INVALID_PARAMETER; 205 if (err == GUAC_RDP_FS_ENOSYS) return STATUS_NOT_IMPLEMENTED; 206 if (err == GUAC_RDP_FS_ENOTSUP) return STATUS_NOT_SUPPORTED; 207 208 /* Default to invalid parameter */ 209 return STATUS_INVALID_PARAMETER; 210 211} 212 213int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path, 214 int access, int file_attributes, int create_disposition, 215 int create_options) { 216 217 char real_path[GUAC_RDP_FS_MAX_PATH]; 218 char normalized_path[GUAC_RDP_FS_MAX_PATH]; 219 220 struct stat file_stat; 221 int fd; 222 int file_id; 223 guac_rdp_fs_file* file; 224 225 int flags = 0; 226 227 guac_client_log(fs->client, GUAC_LOG_DEBUG, 228 "%s: path=\"%s\", access=0x%x, file_attributes=0x%x, " 229 "create_disposition=0x%x, create_options=0x%x", 230 __func__, path, access, file_attributes, 231 create_disposition, create_options); 232 233 /* If no files available, return too many open */ 234 if (fs->open_files >= GUAC_RDP_FS_MAX_FILES) { 235 guac_client_log(fs->client, GUAC_LOG_DEBUG, 236 "%s: Too many open files.", 237 __func__, path); 238 return GUAC_RDP_FS_ENFILE; 239 } 240 241 /* If path empty, transform to root path */ 242 if (path[0] == '\0') 243 path = "\\"; 244 245 /* If path is relative, the file does not exist */ 246 else if (path[0] != '\\' && path[0] != '/') { 247 guac_client_log(fs->client, GUAC_LOG_DEBUG, 248 "%s: Access denied - supplied path \"%s\" is relative.", 249 __func__, path); 250 return GUAC_RDP_FS_ENOENT; 251 } 252 253 /* Translate access into flags */ 254 if (access & GENERIC_ALL) 255 flags = O_RDWR; 256 else if ((access & ( GENERIC_WRITE 257 | FILE_WRITE_DATA 258 | FILE_APPEND_DATA)) 259 && (access & (GENERIC_READ | FILE_READ_DATA))) 260 flags = O_RDWR; 261 else if (access & ( GENERIC_WRITE 262 | FILE_WRITE_DATA 263 | FILE_APPEND_DATA)) 264 flags = O_WRONLY; 265 else 266 flags = O_RDONLY; 267 268 /* Normalize path, return no-such-file if invalid */ 269 if (guac_rdp_fs_normalize_path(path, normalized_path)) { 270 guac_client_log(fs->client, GUAC_LOG_DEBUG, 271 "%s: Normalization of path \"%s\" failed.", __func__, path); 272 return GUAC_RDP_FS_ENOENT; 273 } 274 275 guac_client_log(fs->client, GUAC_LOG_DEBUG, 276 "%s: Normalized path \"%s\" to \"%s\".", 277 __func__, path, normalized_path); 278 279 /* Translate normalized path to real path */ 280 __guac_rdp_fs_translate_path(fs, normalized_path, real_path); 281 282 guac_client_log(fs->client, GUAC_LOG_DEBUG, 283 "%s: Translated path \"%s\" to \"%s\".", 284 __func__, normalized_path, real_path); 285 286 switch (create_disposition) { 287 288 /* Create if not exist, fail otherwise */ 289 case FILE_CREATE: 290 flags |= O_CREAT | O_EXCL; 291 break; 292 293 /* Open file if exists and do not overwrite, fail otherwise */ 294 case FILE_OPEN: 295 /* No flag necessary - default functionality of open */ 296 break; 297 298 /* Open if exists, create otherwise */ 299 case FILE_OPEN_IF: 300 flags |= O_CREAT; 301 break; 302 303 /* Overwrite if exists, fail otherwise */ 304 case FILE_OVERWRITE: 305 flags |= O_TRUNC; 306 break; 307 308 /* Overwrite if exists, create otherwise */ 309 case FILE_OVERWRITE_IF: 310 flags |= O_CREAT | O_TRUNC; 311 break; 312 313 /* Supersede (replace) if exists, otherwise create */ 314 case FILE_SUPERSEDE: 315 unlink(real_path); 316 flags |= O_CREAT | O_TRUNC; 317 break; 318 319 /* Unrecognised disposition */ 320 default: 321 return GUAC_RDP_FS_ENOSYS; 322 323 } 324 325 /* Create directory first, if necessary */ 326 if ((create_options & FILE_DIRECTORY_FILE) && (flags & O_CREAT)) { 327 328 /* Create directory */ 329 if (mkdir(real_path, S_IRWXU)) { 330 if (errno != EEXIST || (flags & O_EXCL)) { 331 guac_client_log(fs->client, GUAC_LOG_DEBUG, 332 "%s: mkdir() failed: %s", 333 __func__, strerror(errno)); 334 return guac_rdp_fs_get_errorcode(errno); 335 } 336 } 337 338 /* Unset O_CREAT and O_EXCL as directory must exist before open() */ 339 flags &= ~(O_CREAT | O_EXCL); 340 341 } 342 343 guac_client_log(fs->client, GUAC_LOG_DEBUG, 344 "%s: native open: real_path=\"%s\", flags=0x%x", 345 __func__, real_path, flags); 346 347 /* Open file */ 348 fd = open(real_path, flags, S_IRUSR | S_IWUSR); 349 350 /* If file open failed as we're trying to write a dir, retry as read-only */ 351 if (fd == -1 && errno == EISDIR) { 352 flags &= ~(O_WRONLY | O_RDWR); 353 flags |= O_RDONLY; 354 fd = open(real_path, flags, S_IRUSR | S_IWUSR); 355 } 356 357 if (fd == -1) { 358 guac_client_log(fs->client, GUAC_LOG_DEBUG, 359 "%s: open() failed: %s", __func__, strerror(errno)); 360 return guac_rdp_fs_get_errorcode(errno); 361 } 362 363 /* Get file ID, init file */ 364 file_id = guac_pool_next_int(fs->file_id_pool); 365 file = &(fs->files[file_id]); 366 file->id = file_id; 367 file->fd = fd; 368 file->dir = NULL; 369 file->dir_pattern[0] = '\0'; 370 file->absolute_path = guac_strdup(normalized_path); 371 file->real_path = guac_strdup(real_path); 372 file->bytes_written = 0; 373 374 guac_client_log(fs->client, GUAC_LOG_DEBUG, 375 "%s: Opened \"%s\" as file_id=%i", 376 __func__, normalized_path, file_id); 377 378 /* Attempt to pull file information */ 379 if (fstat(fd, &file_stat) == 0) { 380 381 /* Load size and times */ 382 file->size = file_stat.st_size; 383 file->ctime = WINDOWS_TIME(file_stat.st_ctime); 384 file->mtime = WINDOWS_TIME(file_stat.st_mtime); 385 file->atime = WINDOWS_TIME(file_stat.st_atime); 386 387 /* Set type */ 388 if (S_ISDIR(file_stat.st_mode)) 389 file->attributes = FILE_ATTRIBUTE_DIRECTORY; 390 else 391 file->attributes = FILE_ATTRIBUTE_NORMAL; 392 393 } 394 395 /* If information cannot be retrieved, fake it */ 396 else { 397 398 /* Init information to 0, lacking any alternative */ 399 file->size = 0; 400 file->ctime = 0; 401 file->mtime = 0; 402 file->atime = 0; 403 file->attributes = FILE_ATTRIBUTE_NORMAL; 404 405 } 406 407 fs->open_files++; 408 409 return file_id; 410 411} 412 413int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset, 414 void* buffer, int length) { 415 416 int bytes_read; 417 418 guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id); 419 if (file == NULL) { 420 guac_client_log(fs->client, GUAC_LOG_DEBUG, 421 "%s: Read from bad file_id: %i", __func__, file_id); 422 return GUAC_RDP_FS_EINVAL; 423 } 424 425 /* Attempt read */ 426 lseek(file->fd, offset, SEEK_SET); 427 bytes_read = read(file->fd, buffer, length); 428 429 /* Translate errno on error */ 430 if (bytes_read < 0) 431 return guac_rdp_fs_get_errorcode(errno); 432 433 return bytes_read; 434 435} 436 437int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, uint64_t offset, 438 void* buffer, int length) { 439 440 int bytes_written; 441 442 guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id); 443 if (file == NULL) { 444 guac_client_log(fs->client, GUAC_LOG_DEBUG, 445 "%s: Write to bad file_id: %i", __func__, file_id); 446 return GUAC_RDP_FS_EINVAL; 447 } 448 449 /* Attempt write */ 450 lseek(file->fd, offset, SEEK_SET); 451 bytes_written = write(file->fd, buffer, length); 452 453 /* Translate errno on error */ 454 if (bytes_written < 0) 455 return guac_rdp_fs_get_errorcode(errno); 456 457 file->bytes_written += bytes_written; 458 return bytes_written; 459 460} 461 462int guac_rdp_fs_rename(guac_rdp_fs* fs, int file_id, 463 const char* new_path) { 464 465 char real_path[GUAC_RDP_FS_MAX_PATH]; 466 char normalized_path[GUAC_RDP_FS_MAX_PATH]; 467 468 guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id); 469 if (file == NULL) { 470 guac_client_log(fs->client, GUAC_LOG_DEBUG, 471 "%s: Rename of bad file_id: %i", __func__, file_id); 472 return GUAC_RDP_FS_EINVAL; 473 } 474 475 /* Normalize path, return no-such-file if invalid */ 476 if (guac_rdp_fs_normalize_path(new_path, normalized_path)) { 477 guac_client_log(fs->client, GUAC_LOG_DEBUG, 478 "%s: Normalization of path \"%s\" failed.", 479 __func__, new_path); 480 return GUAC_RDP_FS_ENOENT; 481 } 482 483 /* Translate normalized path to real path */ 484 __guac_rdp_fs_translate_path(fs, normalized_path, real_path); 485 486 guac_client_log(fs->client, GUAC_LOG_DEBUG, 487 "%s: Renaming \"%s\" -> \"%s\"", 488 __func__, file->real_path, real_path); 489 490 /* Perform rename */ 491 if (rename(file->real_path, real_path)) { 492 guac_client_log(fs->client, GUAC_LOG_DEBUG, 493 "%s: rename() failed: \"%s\" -> \"%s\"", 494 __func__, file->real_path, real_path); 495 return guac_rdp_fs_get_errorcode(errno); 496 } 497 498 return 0; 499 500} 501 502int guac_rdp_fs_delete(guac_rdp_fs* fs, int file_id) { 503 504 /* Get file */ 505 guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id); 506 if (file == NULL) { 507 guac_client_log(fs->client, GUAC_LOG_DEBUG, 508 "%s: Delete of bad file_id: %i", __func__, file_id); 509 return GUAC_RDP_FS_EINVAL; 510 } 511 512 /* If directory, attempt removal */ 513 if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) { 514 if (rmdir(file->real_path)) { 515 guac_client_log(fs->client, GUAC_LOG_DEBUG, 516 "%s: rmdir() failed: \"%s\"", __func__, file->real_path); 517 return guac_rdp_fs_get_errorcode(errno); 518 } 519 } 520 521 /* Otherwise, attempt deletion */ 522 else if (unlink(file->real_path)) { 523 guac_client_log(fs->client, GUAC_LOG_DEBUG, 524 "%s: unlink() failed: \"%s\"", __func__, file->real_path); 525 return guac_rdp_fs_get_errorcode(errno); 526 } 527 528 return 0; 529 530} 531 532int guac_rdp_fs_truncate(guac_rdp_fs* fs, int file_id, int length) { 533 534 /* Get file */ 535 guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id); 536 if (file == NULL) { 537 guac_client_log(fs->client, GUAC_LOG_DEBUG, 538 "%s: Delete of bad file_id: %i", __func__, file_id); 539 return GUAC_RDP_FS_EINVAL; 540 } 541 542 /* Attempt truncate */ 543 if (ftruncate(file->fd, length)) { 544 guac_client_log(fs->client, GUAC_LOG_DEBUG, 545 "%s: ftruncate() to %i bytes failed: \"%s\"", 546 __func__, length, file->real_path); 547 return guac_rdp_fs_get_errorcode(errno); 548 } 549 550 return 0; 551 552} 553 554void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id) { 555 556 guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id); 557 if (file == NULL) { 558 guac_client_log(fs->client, GUAC_LOG_DEBUG, 559 "%s: Ignoring close for bad file_id: %i", 560 __func__, file_id); 561 return; 562 } 563 564 file = &(fs->files[file_id]); 565 566 guac_client_log(fs->client, GUAC_LOG_DEBUG, 567 "%s: Closed \"%s\" (file_id=%i)", 568 __func__, file->absolute_path, file_id); 569 570 /* Close directory, if open */ 571 if (file->dir != NULL) 572 closedir(file->dir); 573 574 /* Close file */ 575 close(file->fd); 576 577 /* Free name */ 578 guac_mem_free(file->absolute_path); 579 guac_mem_free(file->real_path); 580 581 /* Free ID back to pool */ 582 guac_pool_free_int(fs->file_id_pool, file_id); 583 fs->open_files--; 584 585} 586 587const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id) { 588 589 guac_rdp_fs_file* file; 590 591 struct dirent* result; 592 593 /* Only read if file ID is valid */ 594 if (file_id < 0 || file_id >= GUAC_RDP_FS_MAX_FILES) 595 return NULL; 596 597 file = &(fs->files[file_id]); 598 599 /* Open directory if not yet open, stop if error */ 600 if (file->dir == NULL) { 601 file->dir = fdopendir(file->fd); 602 if (file->dir == NULL) 603 return NULL; 604 } 605 606 /* Read next entry, stop if error or no more entries */ 607 if ((result = readdir(file->dir)) == NULL) 608 return NULL; 609 610 /* Return filename */ 611 return result->d_name; 612 613} 614 615const char* guac_rdp_fs_basename(const char* path) { 616 617 for (const char* c = path; *c != '\0'; c++) { 618 619 /* Reset beginning of path if a path separator is found */ 620 if (*c == '/' || *c == '\\') 621 path = c + 1; 622 623 } 624 625 /* path now points to the first character after the last path separator */ 626 return path; 627 628} 629 630int guac_rdp_fs_normalize_path(const char* path, char* abs_path) { 631 632 int path_depth = 0; 633 const char* path_components[GUAC_RDP_MAX_PATH_DEPTH]; 634 635 /* If original path is not absolute, normalization fails */ 636 if (path[0] != '\\' && path[0] != '/') 637 return 1; 638 639 /* Create scratch copy of path excluding leading slash (we will be 640 * replacing path separators with null terminators and referencing those 641 * substrings directly as path components) */ 642 char path_scratch[GUAC_RDP_FS_MAX_PATH - 1]; 643 int length = guac_strlcpy(path_scratch, path + 1, 644 sizeof(path_scratch)); 645 646 /* Fail if provided path is too long */ 647 if (length >= sizeof(path_scratch)) 648 return 1; 649 650 /* Locate all path components within path */ 651 const char* current_path_component = &(path_scratch[0]); 652 for (int i = 0; i <= length; i++) { 653 654 /* If current character is a path separator, parse as component */ 655 char c = path_scratch[i]; 656 if (c == '/' || c == '\\' || c == '\0') { 657 658 /* Terminate current component */ 659 path_scratch[i] = '\0'; 660 661 /* If component refers to parent, just move up in depth */ 662 if (strcmp(current_path_component, "..") == 0) { 663 if (path_depth > 0) 664 path_depth--; 665 } 666 667 /* Otherwise, if component not current directory, add to list */ 668 else if (strcmp(current_path_component, ".") != 0 669 && strcmp(current_path_component, "") != 0) { 670 671 /* Fail normalization if path is too deep */ 672 if (path_depth >= GUAC_RDP_MAX_PATH_DEPTH) 673 return 1; 674 675 path_components[path_depth++] = current_path_component; 676 677 } 678 679 /* Update start of next component */ 680 current_path_component = &(path_scratch[i+1]); 681 682 } /* end if separator */ 683 684 /* We do not currently support named streams */ 685 else if (c == ':') 686 return 1; 687 688 } /* end for each character */ 689 690 /* Add leading slash for resulting absolute path */ 691 abs_path[0] = '\\'; 692 693 /* Append normalized components to path, separated by slashes */ 694 guac_strljoin(abs_path + 1, path_components, path_depth, 695 "\\", GUAC_RDP_FS_MAX_PATH - 1); 696 697 return 0; 698 699} 700 701int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path) { 702 703 int length; 704 char combined_path[GUAC_RDP_FS_MAX_PATH]; 705 706 /* Copy parent path */ 707 length = guac_strlcpy(combined_path, parent, sizeof(combined_path)); 708 709 /* Add trailing slash */ 710 length += guac_strlcpy(combined_path + length, "\\", 711 sizeof(combined_path) - length); 712 713 /* Copy remaining path */ 714 length += guac_strlcpy(combined_path + length, rel_path, 715 sizeof(combined_path) - length); 716 717 /* Normalize into provided buffer */ 718 return guac_rdp_fs_normalize_path(combined_path, abs_path); 719 720} 721 722guac_rdp_fs_file* guac_rdp_fs_get_file(guac_rdp_fs* fs, int file_id) { 723 724 /* Validate ID */ 725 if (file_id < 0 || file_id >= GUAC_RDP_FS_MAX_FILES) 726 return NULL; 727 728 /* Return file at given ID */ 729 return &(fs->files[file_id]); 730 731} 732 733int guac_rdp_fs_matches(const char* filename, const char* pattern) { 734 return fnmatch(pattern, filename, FNM_NOESCAPE) != 0; 735} 736 737int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info) { 738 739 /* Read FS information */ 740 struct statvfs fs_stat; 741 if (statvfs(fs->drive_path, &fs_stat)) 742 return guac_rdp_fs_get_errorcode(errno); 743 744 /* Assign to structure */ 745 info->blocks_available = fs_stat.f_bfree; 746 info->blocks_total = fs_stat.f_blocks; 747 info->block_size = fs_stat.f_bsize; 748 return 0; 749 750} 751 752int guac_rdp_fs_append_filename(char* fullpath, const char* path, 753 const char* filename) { 754 755 int i; 756 757 /* Disallow "." as a filename */ 758 if (strcmp(filename, ".") == 0) 759 return 0; 760 761 /* Disallow ".." as a filename */ 762 if (strcmp(filename, "..") == 0) 763 return 0; 764 765 /* Copy path, append trailing slash */ 766 for (i=0; i<GUAC_RDP_FS_MAX_PATH; i++) { 767 768 /* 769 * Append trailing slash only if: 770 * 1) Trailing slash is not already present 771 * 2) Path is non-empty 772 */ 773 774 char c = path[i]; 775 if (c == '\0') { 776 if (i > 0 && path[i-1] != '/' && path[i-1] != '\\') 777 fullpath[i++] = '/'; 778 break; 779 } 780 781 /* Copy character if not end of string */ 782 fullpath[i] = c; 783 784 } 785 786 /* Append filename */ 787 for (; i<GUAC_RDP_FS_MAX_PATH; i++) { 788 789 char c = *(filename++); 790 if (c == '\0') 791 break; 792 793 /* Filenames may not contain slashes */ 794 if (c == '\\' || c == '/') 795 return 0; 796 797 /* Append each character within filename */ 798 fullpath[i] = c; 799 800 } 801 802 /* Verify path length is within maximum */ 803 if (i == GUAC_RDP_FS_MAX_PATH) 804 return 0; 805 806 /* Terminate path string */ 807 fullpath[i] = '\0'; 808 809 /* Append was successful */ 810 return 1; 811 812} 813