root/src/dps8/safemove.h

/* [previous][next][first][last][top][bottom][index][help] */

INCLUDED FROM


DEFINITIONS

This source file includes following definitions.
  1. set_op_not_supported
  2. windows_safe_rename_noreplace
  3. x_unlink
  4. x_link
  5. x_read
  6. x_write
  7. set_mode_owner_times
  8. try_linux_rename_noreplace
  9. copy_noreplace_then_unlink
  10. link_then_unlink_move
  11. posix_safe_rename_noreplace

   1 /*
   2  * vim: filetype=c:tabstop=2:softtabstop=2:shiftwidth=2:ai:expandtab
   3  * SPDX-License-Identifier: ICU
   4  * scspell-id: 91afe2c2-888f-11f0-a7a2-80ee73e9b8e7
   5  *
   6  * ----------------------------------------------------------------------------
   7  *
   8  * Copyright (c) 2025 Jeffrey H. Johnson
   9  * Copyright (c) 2025 The DPS8M Development Team
  10  *
  11  * This software is made available under the terms of the ICU License.
  12  * See the LICENSE.md file at the top-level directory of this distribution.
  13  *
  14  * ----------------------------------------------------------------------------
  15  */
  16 
  17 ////////////////////////////////////////////////////////////////////////////////////////////////////
  18 
  19 #if !defined(SAFEMOVE_H)
  20 # define SAFEMOVE_H
  21 
  22 ////////////////////////////////////////////////////////////////////////////////////////////////////
  23 
  24 # if !defined(_POSIX_C_SOURCE)
  25 #  define _POSIX_C_SOURCE 200809L
  26 # endif
  27 
  28 # if !defined(_GNU_SOURCE)
  29 #  define _GNU_SOURCE
  30 # endif
  31 
  32 # if !defined(_NETBSD_SOURCE)
  33 #  define _NETBSD_SOURCE
  34 # endif
  35 
  36 # if !defined(_OPENBSD_SOURCE)
  37 #  define _OPENBSD_SOURCE
  38 # endif
  39 
  40 ////////////////////////////////////////////////////////////////////////////////////////////////////
  41 
  42 # include <errno.h>
  43 # include <fcntl.h>
  44 # include <limits.h>
  45 # include <stdarg.h>
  46 # include <stdbool.h>
  47 # include <stddef.h>
  48 # include <stdint.h>
  49 # include <stdio.h>
  50 # include <stdlib.h>
  51 # include <string.h>
  52 # include <sys/stat.h>
  53 # include <sys/types.h>
  54 # include <unistd.h>
  55 # include <utime.h>
  56 
  57 ////////////////////////////////////////////////////////////////////////////////////////////////////
  58 
  59 # undef HAS_INCLUDE
  60 # if defined __has_include
  61 #  define HAS_INCLUDE(inc) __has_include(inc)
  62 # else
  63 #  define HAS_INCLUDE(inc) 0
  64 # endif
  65 
  66 ////////////////////////////////////////////////////////////////////////////////////////////////////
  67 
  68 # if defined(__linux__)
  69 #  if HAS_INCLUDE(<linux/fs.h>)
  70 #   include <linux/fs.h>
  71 #  endif
  72 #  if HAS_INCLUDE(<sys/syscall.h>)
  73 #   include <sys/syscall.h>
  74 #  endif
  75 # endif
  76 
  77 ////////////////////////////////////////////////////////////////////////////////////////////////////
  78 
  79 # if defined(_WIN32)
  80 #  include <windows.h>
  81 #  include <fileapi.h>
  82 # endif
  83 
  84 ////////////////////////////////////////////////////////////////////////////////////////////////////
  85 
  86 # if defined(FREE)
  87 #  undef FREE
  88 # endif /* if defined(FREE) */
  89 # define FREE(p) do \
  90     {               \
  91       free ((p));   \
  92       (p) = NULL;   \
  93     }               \
  94   while (0)
  95 
  96 ////////////////////////////////////////////////////////////////////////////////////////////////////
  97 
  98 static void
  99 set_op_not_supported (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 100 {
 101 # if defined(ENOTSUP)
 102   errno = ENOTSUP;
 103 # elif defined(EOPNOTSUPP)
 104   errno = EOPNOTSUPP;
 105 # else
 106   errno = ENOSYS;
 107 # endif
 108 }
 109 
 110 ////////////////////////////////////////////////////////////////////////////////////////////////////
 111 
 112 # if defined(_WIN32)
 113 static int
 114 windows_safe_rename_noreplace (const char * src, const char * dst)
     /* [previous][next][first][last][top][bottom][index][help] */
 115 {
 116   if (! src || ! dst)
 117     {
 118       errno = EINVAL;
 119       return -1;
 120     }
 121 
 122   wchar_t wsrc [MAX_PATH], wdst [MAX_PATH];
 123   if (MultiByteToWideChar (CP_UTF8, 0, src, -1, wsrc, MAX_PATH) == 0 ||
 124       MultiByteToWideChar (CP_UTF8, 0, dst, -1, wdst, MAX_PATH) == 0)
 125     {
 126       errno = EINVAL;
 127       return -1;
 128     }
 129 
 130   DWORD attr = GetFileAttributesW (wsrc);
 131   if (attr == INVALID_FILE_ATTRIBUTES)
 132     {
 133       errno = ENOENT;
 134       return -1;
 135     }
 136 
 137   if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
 138     {
 139       set_op_not_supported ();
 140       return -1;
 141     }
 142 
 143   if (_stricmp (src, dst) == 0)
 144     return 0;
 145 
 146   if (MoveFileExW (wsrc, wdst, MOVEFILE_WRITE_THROUGH))
 147     return 0;
 148 
 149   DWORD err = GetLastError ();
 150   if (err == ERROR_FILE_EXISTS || err == ERROR_ALREADY_EXISTS)
 151     {
 152       errno = EEXIST;
 153       return -1;
 154     }
 155 
 156   if (err == ERROR_NOT_SAME_DEVICE)
 157     {
 158       DWORD sattr = GetFileAttributesW (wsrc);
 159 
 160       if (sattr == INVALID_FILE_ATTRIBUTES)
 161         {
 162           errno = ENOENT;
 163           return -1;
 164         }
 165 
 166       if (sattr & FILE_ATTRIBUTE_DIRECTORY)
 167         {
 168           errno = EISDIR;
 169           return -1;
 170         }
 171 
 172       if (! CopyFileW (wsrc, wdst, TRUE))
 173         {
 174           DWORD cerr = GetLastError ();
 175 
 176           if (cerr == ERROR_FILE_EXISTS || cerr == ERROR_ALREADY_EXISTS)
 177             errno = EEXIST;
 178           else
 179             errno = EIO;
 180 
 181           return -1;
 182         }
 183 
 184       if (! DeleteFileW (wsrc))
 185         {
 186           DeleteFileW (wdst);
 187           errno = EIO;
 188           return -1;
 189         }
 190 
 191       return 0;
 192     }
 193 
 194   errno = EIO;
 195 
 196   return -1;
 197 }
 198 
 199 ////////////////////////////////////////////////////////////////////////////////////////////////////
 200 
 201 #  define safe_rename_noreplace(src, dst) windows_safe_rename_noreplace(src, dst)
 202 # else
 203 
 204 ////////////////////////////////////////////////////////////////////////////////////////////////////
 205 
 206 static int
 207 x_unlink (const char * path)
     /* [previous][next][first][last][top][bottom][index][help] */
 208 {
 209   int rc;
 210   do
 211     rc = unlink (path);
 212   while (rc == -1 && errno == EINTR);
 213 
 214   return rc;
 215 }
 216 
 217 ////////////////////////////////////////////////////////////////////////////////////////////////////
 218 
 219 static int
 220 x_link (const char * oldp, const char * newp)
     /* [previous][next][first][last][top][bottom][index][help] */
 221 {
 222   int rc;
 223   do
 224     rc = link (oldp, newp);
 225   while (rc == -1 && errno == EINTR);
 226 
 227   return rc;
 228 }
 229 
 230 ////////////////////////////////////////////////////////////////////////////////////////////////////
 231 
 232 static ssize_t
 233 x_read (int fd, void * buf, size_t sz)
     /* [previous][next][first][last][top][bottom][index][help] */
 234 {
 235   for (;;)
 236     {
 237       ssize_t n = read (fd, buf, sz);
 238 
 239       if (n >= 0)
 240         return n;
 241 
 242       if (errno != EINTR)
 243         return -1;
 244     }
 245 }
 246 
 247 ////////////////////////////////////////////////////////////////////////////////////////////////////
 248 
 249 static ssize_t
 250 x_write (int fd, const void * buf, size_t sz)
     /* [previous][next][first][last][top][bottom][index][help] */
 251 {
 252   const unsigned char * p = (const unsigned char *)buf;
 253   size_t left = sz;
 254 
 255   while (left)
 256     {
 257       ssize_t n = write (fd, p, left);
 258 
 259       if (n < 0)
 260         {
 261           if (errno == EINTR)
 262             continue;
 263 
 264           return -1;
 265         }
 266 
 267       p    += n;
 268       left -= (size_t)n;
 269     }
 270 
 271   return (ssize_t)sz;
 272 }
 273 
 274 ////////////////////////////////////////////////////////////////////////////////////////////////////
 275 
 276 static int
 277 set_mode_owner_times (const char * dst_path, int dst_fd, const struct stat * st)
     /* [previous][next][first][last][top][bottom][index][help] */
 278 {
 279   if (fchown (dst_fd, st -> st_uid, st -> st_gid) == -1 && errno != EPERM)
 280     return -1;
 281 
 282   if (fchmod (dst_fd, st -> st_mode & 07777) == -1)
 283     return -1;
 284 
 285   struct utimbuf ut =
 286     {
 287       . actime  = st -> st_atime,
 288       . modtime = st -> st_mtime
 289     };
 290 
 291   if (utime (dst_path, & ut) == -1)
 292     return -1;
 293 
 294   return 0;
 295 }
 296 
 297 ////////////////////////////////////////////////////////////////////////////////////////////////////
 298 
 299 static int
 300 try_linux_rename_noreplace (const char * src, const char * dst, int * handled)
     /* [previous][next][first][last][top][bottom][index][help] */
 301 {
 302   * handled = 0;
 303 #  if defined(__linux__) && defined(AT_FDCWD) && defined(RENAME_NOREPLACE)
 304 #   if defined(SYS_renameat2)
 305   int rc = (int)syscall (SYS_renameat2, AT_FDCWD, src, AT_FDCWD, dst, RENAME_NOREPLACE);
 306 
 307   if (rc == 0)
 308     {
 309       * handled = 1;
 310       return 0;
 311     }
 312 
 313   if (rc == -1)
 314     {
 315       if (errno == ENOSYS || errno == EINVAL)
 316         {
 317           * handled = 0; //-V1048
 318           errno     = 0;
 319           return -1;
 320         }
 321 
 322       if (errno == EXDEV)
 323         {
 324           * handled = 1;
 325           return -1;
 326         }
 327 
 328       * handled = 1; //-V523
 329 
 330       return -1;
 331     }
 332 #   else
 333   (void)src;
 334   (void)dst;
 335 #   endif
 336 #  endif
 337   return -1;
 338 }
 339 
 340 ////////////////////////////////////////////////////////////////////////////////////////////////////
 341 
 342 static int
 343 copy_noreplace_then_unlink (const char * src, const char * dst, const struct stat * src_st)
     /* [previous][next][first][last][top][bottom][index][help] */
 344 {
 345   int sfd = -1, dfd = -1;
 346   int rc  = -1;
 347 
 348   sfd = open (src, O_RDONLY);
 349 
 350   if (sfd == -1)
 351     goto safemove_out;
 352 
 353   struct stat lst;
 354 
 355   if (src_st)
 356     lst = * src_st;
 357   else if (fstat (sfd, & lst) == -1)
 358     goto safemove_out;
 359 
 360   if (! S_ISREG (lst . st_mode))
 361     {
 362       errno = EXDEV;
 363       goto safemove_out;
 364     }
 365 
 366   dfd = open (dst, O_WRONLY | O_CREAT | O_EXCL, 0600);
 367 
 368   if (dfd == -1)
 369     goto safemove_out;
 370 
 371   const size_t BUFSZ = 1 << 20;
 372 
 373   void * buf = malloc (BUFSZ);
 374   if (! buf)
 375     {
 376       errno = ENOMEM;
 377       goto safemove_out;
 378     }
 379 
 380   for (;;)
 381     {
 382       ssize_t n = x_read (sfd, buf, BUFSZ);
 383 
 384       if (n < 0)
 385         {
 386           FREE (buf);
 387           goto safemove_out;
 388         }
 389 
 390       if (n == 0)
 391         {
 392           FREE (buf);
 393           break;
 394         }
 395 
 396       if (x_write (dfd, buf, (size_t)n) < 0)
 397         {
 398           FREE (buf);
 399           goto safemove_out;
 400         }
 401     }
 402 
 403   (void)fsync (dfd);
 404 
 405   if (set_mode_owner_times (dst, dfd, & lst) == -1)
 406     goto safemove_out;
 407 
 408   if (close (dfd) == -1)
 409     {
 410       dfd = -1;
 411       goto safemove_out;
 412     }
 413 
 414   dfd = -1;
 415 
 416   if (x_unlink (src) == -1)
 417     {
 418       int e = errno;
 419       (void)x_unlink (dst);
 420       errno = e;
 421       goto safemove_out;
 422     }
 423 
 424   rc = 0;
 425 
 426 safemove_out:
 427   {
 428     int saved = errno;
 429 
 430     if (sfd != -1)
 431       (void)close (sfd);
 432 
 433     if (dfd != -1)
 434       {
 435         int e2 = errno;
 436         (void)close (dfd);
 437         errno = e2;
 438       }
 439 
 440     errno = saved;
 441   }
 442 
 443   return rc;
 444 }
 445 
 446 ////////////////////////////////////////////////////////////////////////////////////////////////////
 447 
 448 static int
 449 link_then_unlink_move (const char * src, const char * dst)
     /* [previous][next][first][last][top][bottom][index][help] */
 450 {
 451   if (x_link (src, dst) == -1)
 452     return -1;
 453 
 454   if (x_unlink (src) == -1)
 455     {
 456       int e = errno;
 457       (void)x_unlink (dst);
 458       errno = e;
 459       return -1;
 460     }
 461 
 462   return 0;
 463 }
 464 
 465 ////////////////////////////////////////////////////////////////////////////////////////////////////
 466 
 467 static int
 468 posix_safe_rename_noreplace (const char * src, const char * dst)
     /* [previous][next][first][last][top][bottom][index][help] */
 469 {
 470   if (! src || ! dst)
 471     {
 472       errno = EINVAL;
 473       return -1;
 474     }
 475 
 476   struct stat st;
 477   if (lstat (src, & st) == -1)
 478     return -1;
 479 
 480   if (S_ISLNK (st . st_mode))
 481     {
 482       set_op_not_supported ();
 483       return -1;
 484     }
 485 
 486   if (strcmp (src, dst) == 0)
 487     return 0;
 488 
 489   int handled = 0;
 490 
 491   if (try_linux_rename_noreplace (src, dst, & handled) == 0)
 492     return 0;
 493 
 494   if (handled && errno != EXDEV)
 495     return -1;
 496 
 497   if (S_ISDIR (st . st_mode))
 498     {
 499       errno = EISDIR;
 500       return -1;
 501     }
 502 
 503   if (link_then_unlink_move (src, dst) == 0)
 504     return 0;
 505 
 506   if (errno == EEXIST)
 507     return -1;
 508 
 509   if (errno != EXDEV)
 510     return -1;
 511 
 512   if (copy_noreplace_then_unlink (src, dst, & st) == -1)
 513     return -1;
 514 
 515   return 0;
 516 }
 517 
 518 ////////////////////////////////////////////////////////////////////////////////////////////////////
 519 
 520 #  define safe_rename_noreplace(src, dst) posix_safe_rename_noreplace(src, dst)
 521 # endif
 522 
 523 ////////////////////////////////////////////////////////////////////////////////////////////////////
 524 
 525 # define safe_rename(src, dst) safe_rename_noreplace(src, dst)
 526 
 527 ////////////////////////////////////////////////////////////////////////////////////////////////////
 528 
 529 #endif
 530 
 531 ////////////////////////////////////////////////////////////////////////////////////////////////////

/* [previous][next][first][last][top][bottom][index][help] */