file-utils.c (12867B)
1 /* 2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client 3 * Copyright (C) 1999-2024 the Claws Mail team and Colin Leroy 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #include <glib.h> 20 21 #include <sys/wait.h> 22 23 #include <err.h> 24 #include <errno.h> 25 #include <stdio.h> 26 #include <unistd.h> 27 #include <fcntl.h> 28 29 #include "defs.h" 30 #include "codeconv.h" 31 #include "utils.h" 32 #include "file-utils.h" 33 34 gint file_strip_crs(const gchar *file) 35 { 36 FILE *fp = NULL, *outfp = NULL; 37 gchar buf[4096]; 38 gchar *out = get_tmp_file(); 39 if (file == NULL) 40 goto freeout; 41 42 fp = g_fopen(file, "rb"); 43 if (!fp) 44 goto freeout; 45 46 outfp = g_fopen(out, "wb"); 47 if (!outfp) { 48 fclose(fp); 49 goto freeout; 50 } 51 52 while (fgets(buf, sizeof (buf), fp) != NULL) { 53 strcrchomp(buf); 54 if (fputs(buf, outfp) == EOF) { 55 fclose(fp); 56 fclose(outfp); 57 goto unlinkout; 58 } 59 } 60 61 fclose(fp); 62 if (fclose(outfp) == EOF) { 63 goto unlinkout; 64 } 65 if (rename(out, file) < 0) 66 goto unlinkout; 67 68 g_free(out); 69 return 0; 70 unlinkout: 71 unlink(out); 72 freeout: 73 g_free(out); 74 return -1; 75 } 76 77 /* 78 * Append src file body to the tail of dest file. 79 * Now keep_backup has no effects. 80 */ 81 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup) 82 { 83 FILE *src_fp, *dest_fp; 84 gint n_read; 85 gchar buf[BUFSIZ]; 86 87 gboolean err = FALSE; 88 89 if ((src_fp = g_fopen(src, "rb")) == NULL) { 90 FILE_OP_ERROR(src, "g_fopen"); 91 return -1; 92 } 93 94 if ((dest_fp = g_fopen(dest, "ab")) == NULL) { 95 FILE_OP_ERROR(dest, "g_fopen"); 96 fclose(src_fp); 97 return -1; 98 } 99 100 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) { 101 if (n_read < sizeof(buf) && ferror(src_fp)) 102 break; 103 if (fwrite(buf, 1, n_read, dest_fp) < n_read) { 104 g_warning("writing to %s failed", dest); 105 fclose(dest_fp); 106 fclose(src_fp); 107 unlink(dest); 108 return -1; 109 } 110 } 111 112 if (ferror(src_fp)) { 113 FILE_OP_ERROR(src, "fread"); 114 err = TRUE; 115 } 116 fclose(src_fp); 117 if (fclose(dest_fp) == EOF) { 118 FILE_OP_ERROR(dest, "fclose"); 119 err = TRUE; 120 } 121 122 if (err) { 123 unlink(dest); 124 return -1; 125 } 126 127 return 0; 128 } 129 130 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup) 131 { 132 FILE *src_fp, *dest_fp; 133 gint n_read; 134 gchar buf[BUFSIZ]; 135 gchar *dest_bak = NULL; 136 gboolean err = FALSE; 137 138 if ((src_fp = g_fopen(src, "rb")) == NULL) { 139 FILE_OP_ERROR(src, "g_fopen"); 140 return -1; 141 } 142 if (is_file_exist(dest)) { 143 dest_bak = g_strconcat(dest, ".bak", NULL); 144 if (rename(dest, dest_bak) < 0) { 145 warn("rename %s to %s", dest, dest_bak); 146 fclose(src_fp); 147 g_free(dest_bak); 148 return -1; 149 } 150 } 151 152 if ((dest_fp = g_fopen(dest, "wb")) == NULL) { 153 FILE_OP_ERROR(dest, "g_fopen"); 154 fclose(src_fp); 155 if (dest_bak) { 156 if (rename(dest_bak, dest) < 0) 157 FILE_OP_ERROR(dest_bak, "rename"); 158 g_free(dest_bak); 159 } 160 return -1; 161 } 162 163 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) { 164 if (n_read < sizeof(buf) && ferror(src_fp)) 165 break; 166 if (fwrite(buf, 1, n_read, dest_fp) < n_read) { 167 g_warning("writing to %s failed", dest); 168 fclose(dest_fp); 169 fclose(src_fp); 170 if (unlink(dest) < 0) 171 FILE_OP_ERROR(dest, "unlink"); 172 if (dest_bak) { 173 if (rename(dest_bak, dest) < 0) 174 FILE_OP_ERROR(dest_bak, "rename"); 175 g_free(dest_bak); 176 } 177 return -1; 178 } 179 } 180 181 if (ferror(src_fp)) { 182 FILE_OP_ERROR(src, "fread"); 183 err = TRUE; 184 } 185 fclose(src_fp); 186 if (fclose(dest_fp) == EOF) { 187 FILE_OP_ERROR(dest, "fclose"); 188 err = TRUE; 189 } 190 191 if (err) { 192 if (unlink(dest) < 0) 193 FILE_OP_ERROR(dest, "unlink"); 194 if (dest_bak) { 195 if (rename(dest_bak, dest) < 0) 196 FILE_OP_ERROR(dest_bak, "rename"); 197 g_free(dest_bak); 198 } 199 return -1; 200 } 201 202 if (keep_backup == FALSE && dest_bak) 203 if (unlink(dest_bak) < 0) 204 FILE_OP_ERROR(dest_bak, "unlink"); 205 206 g_free(dest_bak); 207 208 return 0; 209 } 210 211 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp) 212 { 213 gint n_read; 214 gint bytes_left, to_read; 215 gchar buf[BUFSIZ]; 216 217 if (fseek(fp, offset, SEEK_SET) < 0) { 218 perror("fseek"); 219 return -1; 220 } 221 222 bytes_left = length; 223 to_read = MIN(bytes_left, sizeof(buf)); 224 225 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) { 226 if (n_read < to_read && ferror(fp)) 227 break; 228 if (fwrite(buf, 1, n_read, dest_fp) < n_read) { 229 return -1; 230 } 231 bytes_left -= n_read; 232 if (bytes_left == 0) 233 break; 234 to_read = MIN(bytes_left, sizeof(buf)); 235 } 236 237 if (ferror(fp)) { 238 perror("fread"); 239 return -1; 240 } 241 242 return 0; 243 } 244 245 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest) 246 { 247 FILE *dest_fp; 248 gboolean err = FALSE; 249 250 if ((dest_fp = g_fopen(dest, "wb")) == NULL) { 251 FILE_OP_ERROR(dest, "g_fopen"); 252 return -1; 253 } 254 255 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0) 256 err = TRUE; 257 258 if (fclose(dest_fp) == EOF) { 259 FILE_OP_ERROR(dest, "fclose"); 260 err = TRUE; 261 } 262 263 if (err) { 264 g_warning("writing to %s failed", dest); 265 unlink(dest); 266 return -1; 267 } 268 269 return 0; 270 } 271 272 gint canonicalize_file(const gchar *src, const gchar *dest) 273 { 274 FILE *src_fp, *dest_fp; 275 gchar buf[BUFFSIZE]; 276 gint len; 277 gboolean err = FALSE; 278 gboolean last_linebreak = FALSE; 279 280 if (src == NULL || dest == NULL) 281 return -1; 282 283 if ((src_fp = g_fopen(src, "rb")) == NULL) { 284 FILE_OP_ERROR(src, "g_fopen"); 285 return -1; 286 } 287 288 if ((dest_fp = g_fopen(dest, "wb")) == NULL) { 289 FILE_OP_ERROR(dest, "g_fopen"); 290 fclose(src_fp); 291 return -1; 292 } 293 294 while (fgets(buf, sizeof(buf), src_fp) != NULL) { 295 gint r = 0; 296 297 len = strlen(buf); 298 if (len == 0) break; 299 last_linebreak = FALSE; 300 301 if (buf[len - 1] != '\n') { 302 last_linebreak = TRUE; 303 r = fputs(buf, dest_fp); 304 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') { 305 r = fputs(buf, dest_fp); 306 } else { 307 if (len > 1) { 308 r = fwrite(buf, 1, len - 1, dest_fp); 309 if (r != (len -1)) 310 r = EOF; 311 } 312 if (r != EOF) 313 r = fputs("\r\n", dest_fp); 314 } 315 316 if (r == EOF) { 317 g_warning("writing to %s failed", dest); 318 fclose(dest_fp); 319 fclose(src_fp); 320 unlink(dest); 321 return -1; 322 } 323 } 324 325 if (last_linebreak == TRUE) { 326 if (fputs("\r\n", dest_fp) == EOF) 327 err = TRUE; 328 } 329 330 if (ferror(src_fp)) { 331 FILE_OP_ERROR(src, "fgets"); 332 err = TRUE; 333 } 334 fclose(src_fp); 335 if (fclose(dest_fp) == EOF) { 336 FILE_OP_ERROR(dest, "fclose"); 337 err = TRUE; 338 } 339 340 if (err) { 341 unlink(dest); 342 return -1; 343 } 344 345 return 0; 346 } 347 348 gint str_write_to_file(const gchar *str, char *file) 349 { 350 if (strlen(str) == 0) 351 return 0; 352 353 FILE *fp = fopen(file, "wb"); 354 if (fp == NULL) { 355 warn("open %s", file); 356 return -1; 357 } 358 359 size_t len = strlen(str); 360 size_t n = fwrite(str, 1, strlen(str), fp); 361 if (n != len) { 362 warn("short write to %s: expected %d bytes but wrote %d", file, len, n); 363 fclose(fp); 364 remove(file); 365 return -1; 366 } 367 368 if (fclose(fp) == EOF) { 369 warn("close %s", file); 370 remove(file); 371 return -1; 372 } 373 return 0; 374 } 375 376 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode) 377 { 378 GByteArray *array; 379 guchar buf[BUFSIZ]; 380 gint n_read; 381 gchar *str; 382 383 cm_return_val_if_fail(fp != NULL, NULL); 384 385 array = g_byte_array_new(); 386 387 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) { 388 if (n_read < sizeof(buf) && ferror(fp)) 389 break; 390 g_byte_array_append(array, buf, n_read); 391 } 392 393 if (ferror(fp)) { 394 FILE_OP_ERROR("file stream", "fread"); 395 g_byte_array_free(array, TRUE); 396 return NULL; 397 } 398 399 buf[0] = '\0'; 400 g_byte_array_append(array, buf, 1); 401 str = (gchar *)array->data; 402 g_byte_array_free(array, FALSE); 403 404 if (recode && !g_utf8_validate(str, -1, NULL)) { 405 const gchar *src_codeset, *dest_codeset; 406 gchar *tmp = NULL; 407 src_codeset = conv_get_locale_charset_str(); 408 dest_codeset = CS_UTF_8; 409 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset); 410 g_free(str); 411 str = tmp; 412 } 413 414 return str; 415 } 416 417 static gchar *file_read_to_str_full(const gchar *file, gboolean recode) 418 { 419 FILE *fp; 420 gchar *str; 421 GStatBuf s; 422 gint fd, err; 423 struct timeval timeout = {1, 0}; 424 fd_set fds; 425 int fflags = 0; 426 427 cm_return_val_if_fail(file != NULL, NULL); 428 429 if (g_stat(file, &s) != 0) { 430 FILE_OP_ERROR(file, "stat"); 431 return NULL; 432 } 433 if (S_ISDIR(s.st_mode)) { 434 g_warning("%s: is a directory", file); 435 return NULL; 436 } 437 438 /* test whether the file is readable without blocking */ 439 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0); 440 if (fd == -1) { 441 FILE_OP_ERROR(file, "open"); 442 return NULL; 443 } 444 445 FD_ZERO(&fds); 446 FD_SET(fd, &fds); 447 448 /* allow for one second */ 449 err = select(fd+1, &fds, NULL, NULL, &timeout); 450 if (err <= 0 || !FD_ISSET(fd, &fds)) { 451 if (err < 0) { 452 FILE_OP_ERROR(file, "select"); 453 } else { 454 g_warning("%s: doesn't seem readable", file); 455 } 456 close(fd); 457 return NULL; 458 } 459 460 /* Now clear O_NONBLOCK */ 461 if ((fflags = fcntl(fd, F_GETFL)) < 0) { 462 FILE_OP_ERROR(file, "fcntl (F_GETFL)"); 463 close(fd); 464 return NULL; 465 } 466 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) { 467 FILE_OP_ERROR(file, "fcntl (F_SETFL)"); 468 close(fd); 469 return NULL; 470 } 471 472 /* get the FILE pointer */ 473 fp = fdopen(fd, "rb"); 474 475 if (fp == NULL) { 476 FILE_OP_ERROR(file, "fdopen"); 477 close(fd); /* if fp isn't NULL, we'll use fclose instead! */ 478 return NULL; 479 } 480 481 str = file_read_stream_to_str_full(fp, recode); 482 483 fclose(fp); 484 485 return str; 486 } 487 488 gchar *file_read_to_str(const gchar *file) 489 { 490 return file_read_to_str_full(file, TRUE); 491 } 492 gchar *file_read_stream_to_str(FILE *fp) 493 { 494 return file_read_stream_to_str_full(fp, TRUE); 495 } 496 497 gint copy_dir(const gchar *src, const gchar *dst) 498 { 499 GDir *dir; 500 const gchar *name; 501 502 if ((dir = g_dir_open(src, 0, NULL)) == NULL) { 503 warn("open %s", src); 504 return -1; 505 } 506 507 if (mkdir(dst, 0700) < 0) { 508 g_dir_close(dir); 509 return -1; 510 } 511 512 while ((name = g_dir_read_name(dir)) != NULL) { 513 gchar *old_file, *new_file; 514 gint r = 0; 515 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL); 516 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL); 517 debug_print("copying: %s -> %s\n", old_file, new_file); 518 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) { 519 r = copy_file(old_file, new_file, TRUE); 520 } 521 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) { 522 GError *error = NULL; 523 gchar *target = g_file_read_link(old_file, &error); 524 if (error) { 525 g_warning("couldn't read link: %s", error->message); 526 g_error_free(error); 527 } 528 if (target) { 529 r = symlink(target, new_file); 530 g_free(target); 531 } 532 } 533 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) { 534 r = copy_dir(old_file, new_file); 535 } 536 g_free(old_file); 537 g_free(new_file); 538 if (r < 0) { 539 g_dir_close(dir); 540 return r; 541 } 542 } 543 g_dir_close(dir); 544 return 0; 545 } 546 547 FILE *my_tmpfile(void) 548 { 549 const gchar suffix[] = ".XXXXXX"; 550 const gchar *tmpdir; 551 guint tmplen; 552 const gchar *progname; 553 guint proglen; 554 gchar *fname; 555 gint fd; 556 FILE *fp; 557 gchar buf[2]="\0"; 558 559 tmpdir = get_tmp_dir(); 560 tmplen = strlen(tmpdir); 561 progname = g_get_prgname(); 562 if (progname == NULL) 563 progname = "claws-mail"; 564 proglen = strlen(progname); 565 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix), 566 return tmpfile()); 567 568 memcpy(fname, tmpdir, tmplen); 569 fname[tmplen] = G_DIR_SEPARATOR; 570 memcpy(fname + tmplen + 1, progname, proglen); 571 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix)); 572 573 fd = g_mkstemp(fname); 574 if (fd < 0) 575 return tmpfile(); 576 577 unlink(fname); 578 579 /* verify that we can write in the file after unlinking */ 580 if (write(fd, buf, 1) < 0) { 581 close(fd); 582 return tmpfile(); 583 } 584 585 fp = fdopen(fd, "w+b"); 586 if (!fp) 587 close(fd); 588 else { 589 rewind(fp); 590 return fp; 591 } 592 593 return tmpfile(); 594 } 595 596 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename) 597 { 598 int fd; 599 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR); 600 fd = g_mkstemp(*filename); 601 if (fd < 0) 602 return NULL; 603 return fdopen(fd, "w+"); 604 } 605 606 FILE *str_open_as_stream(const gchar *str) 607 { 608 FILE *fp; 609 size_t len; 610 611 cm_return_val_if_fail(str != NULL, NULL); 612 613 fp = my_tmpfile(); 614 if (!fp) { 615 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile"); 616 return NULL; 617 } 618 619 len = strlen(str); 620 if (len == 0) return fp; 621 622 if (fwrite(str, 1, len, fp) != len) { 623 FILE_OP_ERROR("str_open_as_stream", "fwrite"); 624 fclose(fp); 625 return NULL; 626 } 627 628 rewind(fp); 629 return fp; 630 } 631 632 gint prefs_chmod_mode(gchar *chmod_pref) 633 { 634 gint newmode = 0; 635 gchar *tmp; 636 637 if (chmod_pref) { 638 newmode = strtol(chmod_pref, &tmp, 8); 639 if (!(*(chmod_pref) && !(*tmp))) 640 newmode = 0; 641 } 642 643 return newmode; 644 }