VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/posix/path-posix.cpp@ 4029

Last change on this file since 4029 was 4029, checked in by vboxsync, 18 years ago

Added missing checks and a fallback path for RTPathUserHome.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 33.5 KB
Line 
1/* $Id: path-posix.cpp 4029 2007-08-03 22:00:58Z vboxsync $ */
2/** @file
3 * innotek Portable Runtime - Path Manipulation, POSIX.
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#define LOG_GROUP RTLOGGROUP_PATH
27#include <stdlib.h>
28#include <limits.h>
29#include <errno.h>
30#include <unistd.h>
31#include <sys/stat.h>
32#include <sys/time.h>
33#include <stdio.h>
34#include <sys/types.h>
35#include <pwd.h>
36#ifdef RT_OS_DARWIN
37# include <mach-o/dyld.h>
38#endif
39
40#include <iprt/path.h>
41#include <iprt/assert.h>
42#include <iprt/string.h>
43#include <iprt/err.h>
44#include <iprt/log.h>
45#include "internal/path.h"
46#include "internal/fs.h"
47
48#ifdef RT_OS_L4
49# include <l4/vboxserver/vboxserver.h>
50#endif
51
52
53
54
55RTDECL(int) RTPathReal(const char *pszPath, char *pszRealPath, unsigned cchRealPath)
56{
57 /*
58 * Convert input.
59 */
60 char *pszNativePath;
61 int rc = rtPathToNative(&pszNativePath, pszPath);
62 if (RT_SUCCESS(rc))
63 {
64 /*
65 * On POSIX platforms the API doesn't take a length parameter, which makes it
66 * a little bit more work.
67 */
68 char szTmpPath[PATH_MAX + 1];
69 const char *psz = realpath(pszNativePath, szTmpPath);
70 if (psz)
71 {
72 /*
73 * Convert result and copy it to the return buffer.
74 */
75 char *pszUtf8RealPath;
76 rc = rtPathFromNative(&pszUtf8RealPath, szTmpPath);
77 if (RT_SUCCESS(rc))
78 {
79 size_t cch = strlen(pszUtf8RealPath) + 1;
80 if (cch <= cchRealPath)
81 memcpy(pszRealPath, pszUtf8RealPath, cch);
82 else
83 rc = VERR_BUFFER_OVERFLOW;
84 RTStrFree(pszUtf8RealPath);
85 }
86 }
87 else
88 rc = RTErrConvertFromErrno(errno);
89 RTStrFree(pszNativePath);
90 }
91
92 LogFlow(("RTPathReal(%p:{%s}, %p:{%s}, %u): returns %Rrc\n", pszPath, pszPath,
93 pszRealPath, RT_SUCCESS(rc) ? pszRealPath : "<failed>", cchRealPath));
94 return rc;
95}
96
97
98/**
99 * Cleans up a path specifier a little bit.
100 * This includes removing duplicate slashes, uncessary single dots, and
101 * trailing slashes. Also, replaces all RTPATH_SLASH characters with '/'.
102 *
103 * @returns Number of bytes in the clean path.
104 * @param pszPath The path to cleanup.
105 * @remark Borrowed from innotek libc.
106 */
107static int fsCleanPath(char *pszPath)
108{
109 /*
110 * Change to '/' and remove duplicates.
111 */
112 char *pszSrc = pszPath;
113 char *pszTrg = pszPath;
114#ifdef HAVE_UNC
115 int fUnc = 0;
116 if ( RTPATH_IS_SLASH(pszPath[0])
117 && RTPATH_IS_SLASH(pszPath[1]))
118 { /* Skip first slash in a unc path. */
119 pszSrc++;
120 *pszTrg++ = '/';
121 fUnc = 1;
122 }
123#endif
124
125 for (;;)
126 {
127 char ch = *pszSrc++;
128 if (RTPATH_IS_SLASH(ch))
129 {
130 *pszTrg++ = '/';
131 for (;;)
132 {
133 do ch = *pszSrc++;
134 while (RTPATH_IS_SLASH(ch));
135
136 /* Remove '/./' and '/.'. */
137 if (ch != '.' || (*pszSrc && !RTPATH_IS_SLASH(*pszSrc)))
138 break;
139 }
140 }
141 *pszTrg = ch;
142 if (!ch)
143 break;
144 pszTrg++;
145 }
146
147 /*
148 * Remove trailing slash if the path may be pointing to a directory.
149 */
150 int cch = pszTrg - pszPath;
151 if ( cch > 1
152 && RTPATH_IS_SLASH(pszTrg[-1])
153#ifdef HAVE_DRIVE
154 && !RTPATH_IS_VOLSEP(pszTrg[-2])
155#endif
156 && !RTPATH_IS_SLASH(pszTrg[-2]))
157 pszPath[--cch] = '\0';
158
159 return cch;
160}
161
162
163RTDECL(int) RTPathAbs(const char *pszPath, char *pszAbsPath, unsigned cchAbsPath)
164{
165 /*
166 * Convert input.
167 */
168 char *pszNativePath;
169 int rc = rtPathToNative(&pszNativePath, pszPath);
170 if (RT_FAILURE(rc))
171 {
172 LogFlow(("RTPathAbs(%p:{%s}, %p, %d): returns %Rrc\n", pszPath,
173 pszPath, pszAbsPath, cchAbsPath, rc));
174 return rc;
175 }
176
177 /*
178 * On POSIX platforms the API doesn't take a length parameter, which makes it
179 * a little bit more work.
180 */
181 char szTmpPath[PATH_MAX + 1];
182 char *psz = realpath(pszNativePath, szTmpPath);
183 if (!psz)
184 {
185 if (errno == ENOENT || errno == ENOTDIR
186#ifdef RT_OS_OS2
187 /// @todo realpath() returns EIO for non-existent UNC paths like
188 // //server/share/subdir (i.e. when a subdir is specified within
189 // a share). We should either fix realpath() in libc or remove
190 // this todo.
191 || errno == EIO
192#endif
193 )
194 {
195 if (strlen(pszNativePath) <= PATH_MAX)
196 {
197 /*
198 * Iterate the path bit by bit an apply realpath to it.
199 */
200
201 char szTmpSrc[PATH_MAX + 1];
202 strcpy(szTmpSrc, pszNativePath);
203 fsCleanPath(szTmpSrc);
204
205 size_t cch = 0; // current resolved path length
206 char *pszCur = szTmpSrc;
207
208#ifdef HAVE_DRIVE
209 if (pszCur[0] && RTPATH_IS_VOLSEP(pszCur[1]) && pszCur[2] == '/')
210 {
211 psz = szTmpPath;
212 cch = 2;
213 pszCur += 3;
214 }
215#ifdef HAVE_UNC
216 else
217 if (pszCur[0] == '/' && pszCur[1] == '/')
218 {
219 pszCur += 2;
220 char *pszSlash = strchr(pszCur, '/');
221 size_t cchElement = pszSlash ? pszSlash - pszCur : strlen(pszCur);
222 if (cchElement && pszCur[cchElement])
223 {
224 psz = szTmpPath;
225 cch = cchElement + 2;
226 pszCur += cchElement + 1;
227 }
228 else
229 /* we've got just "//server" or "//" */
230 /// @todo (r=dmik) not 100% sure we should fail, but the
231 // above cases are just invalid (incomplete) paths,
232 // no matter that Win32 returns these paths as is.
233 rc = VERR_INVALID_NAME;
234 }
235#endif
236#else
237 if (*pszCur == '/')
238 {
239 psz = szTmpPath;
240 pszCur++;
241 }
242#endif
243 else
244 {
245 /* get the cwd */
246 psz = getcwd(szTmpPath, sizeof(szTmpPath));
247 AssertMsg(psz, ("Couldn't get cwd!\n"));
248 if (psz)
249 {
250#ifdef HAVE_DRIVE
251 if (*pszCur == '/')
252 {
253 cch = 2;
254 pszCur++;
255 }
256 else
257#endif
258 cch = strlen(psz);
259 }
260 else
261 rc = RTErrConvertFromErrno(errno);
262 }
263
264 if (psz)
265 {
266 bool fResolveSymlinks = true;
267 char szTmpPath2[PATH_MAX + 1];
268
269 /* make sure strrchr() will work correctly */
270 psz[cch] = '\0';
271
272 while (*pszCur)
273 {
274 char *pszSlash = strchr(pszCur, '/');
275 size_t cchElement = pszSlash ? pszSlash - pszCur : strlen(pszCur);
276 if (cch + cchElement + 1 > PATH_MAX)
277 {
278 rc = VERR_FILENAME_TOO_LONG;
279 break;
280 }
281
282 if (!strncmp(pszCur, "..", cchElement))
283 {
284 char *pszLastSlash = strrchr(psz, '/');
285#ifdef HAVE_UNC
286 if (pszLastSlash && pszLastSlash > psz &&
287 pszLastSlash[-1] != '/')
288#else
289 if (pszLastSlash)
290#endif
291 {
292 cch = pszLastSlash - psz;
293 psz[cch] = '\0';
294 }
295 /* else: We've reached the root and the parent of
296 * the root is the root. */
297 }
298 else
299 {
300 psz[cch++] = '/';
301 memcpy(psz + cch, pszCur, cchElement);
302 cch += cchElement;
303 psz[cch] = '\0';
304
305 if (fResolveSymlinks)
306 {
307 /* resolve possible symlinks */
308 char *psz2 = realpath(psz, psz == szTmpPath
309 ? szTmpPath2
310 : szTmpPath);
311 if (psz2)
312 {
313 psz = psz2;
314 cch = strlen(psz);
315 }
316 else
317 {
318 if (errno != ENOENT && errno != ENOTDIR
319#ifdef RT_OS_OS2
320 /// @todo see above
321 && errno != EIO
322#endif
323 )
324 {
325 rc = RTErrConvertFromErrno(errno);
326 break;
327 }
328
329 /* no more need to resolve symlinks */
330 fResolveSymlinks = false;
331 }
332 }
333 }
334
335 pszCur += cchElement;
336 /* skip the slash */
337 if (*pszCur)
338 ++pszCur;
339 }
340
341#ifdef HAVE_DRIVE
342 /* check if we're at the root */
343 if (cch == 2 && RTPATH_IS_VOLSEP(psz[1]))
344#else
345 /* if the length is zero here, then we're at the root */
346 if (!cch)
347#endif
348 {
349 psz[cch++] = '/';
350 psz[cch] = '\0';
351 }
352 }
353 }
354 else
355 rc = VERR_FILENAME_TOO_LONG;
356 }
357 else
358 rc = RTErrConvertFromErrno(errno);
359 }
360
361 RTStrFree(pszNativePath);
362
363 if (psz && RT_SUCCESS(rc))
364 {
365 /*
366 * Convert result and copy it to the return buffer.
367 */
368 char *pszUtf8AbsPath;
369 rc = rtPathFromNative(&pszUtf8AbsPath, psz);
370 if (RT_FAILURE(rc))
371 {
372 LogFlow(("RTPathAbs(%p:{%s}, %p, %d): returns %Rrc\n", pszPath,
373 pszPath, pszAbsPath, cchAbsPath, rc));
374 return rc;
375 }
376
377 /* replace '/' back with native RTPATH_SLASH */
378 psz = pszUtf8AbsPath;
379 for (; *psz; psz++)
380 if (*psz == '/')
381 *psz = RTPATH_SLASH;
382
383 unsigned cch = strlen(pszUtf8AbsPath) + 1;
384 if (cch <= cchAbsPath)
385 memcpy(pszAbsPath, pszUtf8AbsPath, cch);
386 else
387 rc = VERR_BUFFER_OVERFLOW;
388 RTStrFree(pszUtf8AbsPath);
389 }
390
391 LogFlow(("RTPathAbs(%p:{%s}, %p:{%s}, %d): returns %Rrc\n", pszPath,
392 pszPath, pszAbsPath, RT_SUCCESS(rc) ? pszAbsPath : "<failed>",
393 cchAbsPath, rc));
394 return rc;
395}
396
397
398RTDECL(int) RTPathProgram(char *pszPath, unsigned cchPath)
399{
400 /*
401 * First time only.
402 */
403 if (!g_szrtProgramPath[0])
404 {
405 /*
406 * Linux have no API for obtaining the executable path, but provides a symbolic link
407 * in the proc file system. Note that readlink is one of the weirdest Unix apis around.
408 *
409 * OS/2 have an api for getting the program file name.
410 */
411/** @todo use RTProcGetExecutableName() */
412#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_SOLARIS)
413# ifdef RT_OS_LINUX
414 int cchLink = readlink("/proc/self/exe", &g_szrtProgramPath[0], sizeof(g_szrtProgramPath) - 1);
415# elif defined(RT_OS_SOLARIS)
416 pid_t curProcId = getpid();
417 char szFileBuf[PATH_MAX + 1];
418 sprintf(szFileBuf, "/proc/%ld/path/a.out", curProcId);
419 int cchLink = readlink(szFileBuf, &g_szrtProgramPath[0], sizeof(g_szrtProgramPath) - 1);
420# else /* RT_OS_FREEBSD: */
421 int cchLink = readlink("/proc/curproc/file", &g_szrtProgramPath[0], sizeof(g_szrtProgramPath) - 1);
422# endif
423 if (cchLink < 0 || cchLink == sizeof(g_szrtProgramPath) - 1)
424 {
425 int rc = RTErrConvertFromErrno(errno);
426 AssertMsgFailed(("couldn't read /proc/self/exe. errno=%d cchLink=%d\n", errno, cchLink));
427 LogFlow(("RTPathProgram(%p, %u): returns %Rrc\n", pszPath, cchPath, rc));
428 return rc;
429 }
430 g_szrtProgramPath[cchLink] = '\0';
431
432#elif defined(RT_OS_OS2) || defined(RT_OS_L4)
433 _execname(g_szrtProgramPath, sizeof(g_szrtProgramPath));
434
435#elif defined(RT_OS_DARWIN)
436 const char *pszImageName = _dyld_get_image_name(0);
437 AssertReturn(pszImageName, VERR_INTERNAL_ERROR);
438 size_t cchImageName = strlen(pszImageName);
439 if (cchImageName >= sizeof(g_szrtProgramPath))
440 AssertReturn(pszImageName, VERR_INTERNAL_ERROR);
441 memcpy(g_szrtProgramPath, pszImageName, cchImageName + 1);
442
443#else
444# error needs porting.
445#endif
446
447 /*
448 * Convert to UTF-8 and strip of the filename.
449 */
450 char *pszTmp = NULL;
451 int rc = rtPathFromNative(&pszTmp, &g_szrtProgramPath[0]);
452 if (RT_FAILURE(rc))
453 {
454 LogFlow(("RTPathProgram(%p, %u): returns %Rrc\n", pszPath, cchPath, rc));
455 return rc;
456 }
457 size_t cch = strlen(pszTmp);
458 if (cch >= sizeof(g_szrtProgramPath))
459 {
460 RTStrFree(pszTmp);
461 LogFlow(("RTPathProgram(%p, %u): returns %Rrc\n", pszPath, cchPath, VERR_BUFFER_OVERFLOW));
462 return VERR_BUFFER_OVERFLOW;
463 }
464 memcpy(g_szrtProgramPath, pszTmp, cch + 1);
465 RTPathStripFilename(g_szrtProgramPath);
466 RTStrFree(pszTmp);
467 }
468
469 /*
470 * Calc the length and check if there is space before copying.
471 */
472 unsigned cch = strlen(g_szrtProgramPath) + 1;
473 if (cch <= cchPath)
474 {
475 memcpy(pszPath, g_szrtProgramPath, cch + 1);
476 LogFlow(("RTPathProgram(%p:{%s}, %u): returns %Rrc\n", pszPath, pszPath, cchPath, VINF_SUCCESS));
477 return VINF_SUCCESS;
478 }
479
480 AssertMsgFailed(("Buffer too small (%d < %d)\n", cchPath, cch));
481 LogFlow(("RTPathProgram(%p, %u): returns %Rrc\n", pszPath, cchPath, VERR_BUFFER_OVERFLOW));
482 return VERR_BUFFER_OVERFLOW;
483}
484
485
486#ifndef RT_OS_L4
487/**
488 * Worker for RTPathUserHome that looks up the home directory
489 * using the getpwuid_r api.
490 *
491 * @returns IPRT status code.
492 * @param pszPath The path buffer.
493 * @param cchPath The size of the buffer.
494 * @param uid The User ID to query the home directory of.
495 */
496static int rtPathUserHomeByPasswd(char *pszPath, size_t cchPath, uid_t uid)
497{
498 /*
499 * The getpwuid_r function uses the passed in buffer to "allocate" any
500 * extra memory it needs. On some systems we should probably use the
501 * sysconf function to find the appropriate buffer size, but since it won't
502 * work everywhere we'll settle with a 5KB buffer and ASSUME that it'll
503 * suffice for even the lengthiest user descriptions...
504 */
505 char achBuffer[5120];
506 struct passwd Passwd;
507 struct passwd *pPasswd;
508 memset(&Passwd, 0, sizeof(Passwd));
509 int rc = getpwuid_r(uid, &Passwd, &achBuffer[0], sizeof(achBuffer), &pPasswd);
510 if (rc != 0)
511 return RTErrConvertFromErrno(rc);
512 if (!pPasswd) /* uid not found in /etc/passwd */
513 return VERR_PATH_NOT_FOUND;
514
515 /*
516 * Check that it isn't empty and that it exists.
517 */
518 struct stat st;
519 if ( !pPasswd->pw_dir
520 || !*pPasswd->pw_dir
521 || stat(pPasswd->pw_dir, &st)
522 || !S_ISDIR(st.st_mode))
523 return VERR_PATH_NOT_FOUND;
524
525 /*
526 * Convert it to UTF-8 and copy it to the return buffer.
527 */
528 char *pszUtf8Path;
529 rc = rtPathFromNative(&pszUtf8Path, pPasswd->pw_dir);
530 if (RT_SUCCESS(rc))
531 {
532 size_t cchHome = strlen(pszUtf8Path);
533 if (cchHome < cchPath)
534 memcpy(pszPath, pszUtf8Path, cchHome + 1);
535 else
536 rc = VERR_BUFFER_OVERFLOW;
537 RTStrFree(pszUtf8Path);
538 }
539 return rc;
540}
541#endif
542
543
544/**
545 * Worker for RTPathUserHome that looks up the home directory
546 * using the HOME environment variable.
547 *
548 * @returns IPRT status code.
549 * @param pszPath The path buffer.
550 * @param cchPath The size of the buffer.
551 */
552static int rtPathUserHomeByEnv(char *pszPath, size_t cchPath)
553{
554 /*
555 * Get HOME env. var it and validate it's existance.
556 */
557 int rc = VERR_PATH_NOT_FOUND;
558 const char *pszHome = getenv("HOME");
559 if (!pszHome)
560
561 {
562 struct stat st;
563 if ( !stat(pszHome, &st)
564 && S_ISDIR(st.st_mode))
565 {
566 /*
567 * Convert it to UTF-8 and copy it to the return buffer.
568 */
569 char *pszUtf8Path;
570 rc = rtPathFromNative(&pszUtf8Path, pszHome);
571 if (RT_SUCCESS(rc))
572 {
573 size_t cchHome = strlen(pszUtf8Path);
574 if (cchHome < cchPath)
575 memcpy(pszPath, pszUtf8Path, cchHome + 1);
576 else
577 rc = VERR_BUFFER_OVERFLOW;
578 RTStrFree(pszUtf8Path);
579 }
580 }
581 }
582 return rc;
583}
584
585
586RTDECL(int) RTPathUserHome(char *pszPath, unsigned cchPath)
587{
588 int rc;
589#ifndef RT_OS_L4
590 /*
591 * We make an exception for the root user and use the system call
592 * getpwuid_r to determine their initial home path instead of
593 * reading it from the $HOME variable. This is because the $HOME
594 * variable does not get changed by sudo (and possibly su and others)
595 * which can cause root-owned files to appear in user's home folders.
596 */
597 uid_t uid = geteuid();
598 if (!uid)
599 rc = rtPathUserHomeByPasswd(pszPath, cchPath, uid);
600 else
601 rc = rtPathUserHomeByEnv(pszPath, cchPath);
602
603 /*
604 * On failure, retry using the alternative method.
605 * (Should perhaps restrict the retry cases a bit more here...)
606 */
607 if ( RT_FAILURE(rc)
608 && rc != VERR_BUFFER_OVERFLOW)
609 {
610 if (!uid)
611 rc = rtPathUserHomeByEnv(pszPath, cchPath);
612 else
613 rc = rtPathUserHomeByPasswd(pszPath, cchPath, uid);
614 }
615#else /* RT_OS_L4 */
616 rc = rtPathUserHomeByEnv(pszPath, cchPath);
617#endif /* RT_OS_L4 */
618
619 LogFlow(("RTPathUserHome(%p:{%s}, %u): returns %Rrc\n", pszPath,
620 RT_SUCCESS(rc) ? pszPath : "<failed>", cchPath, rc));
621 return rc;
622}
623
624
625RTR3DECL(int) RTPathQueryInfo(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs)
626{
627 /*
628 * Validate input.
629 */
630 AssertMsgReturn(VALID_PTR(pszPath), ("%p\n", pszPath), VERR_INVALID_POINTER);
631 AssertReturn(*pszPath, VERR_INVALID_PARAMETER);
632 AssertMsgReturn(VALID_PTR(pObjInfo), ("%p\n", pszPath), VERR_INVALID_POINTER);
633 AssertMsgReturn( enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING
634 && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
635 ("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs),
636 VERR_INVALID_PARAMETER);
637
638 /*
639 * Convert the filename.
640 */
641 char *pszNativePath;
642 int rc = rtPathToNative(&pszNativePath, pszPath);
643 if (RT_SUCCESS(rc))
644 {
645 struct stat Stat;
646 if (!stat(pszNativePath, &Stat))
647 {
648 rtFsConvertStatToObjInfo(pObjInfo, &Stat, pszPath, 0);
649 switch (enmAdditionalAttribs)
650 {
651 case RTFSOBJATTRADD_EASIZE:
652 /** @todo Use SGI extended attribute interface to query EA info. */
653 pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
654 pObjInfo->Attr.u.EASize.cb = 0;
655 break;
656
657 case RTFSOBJATTRADD_NOTHING:
658 case RTFSOBJATTRADD_UNIX:
659 Assert(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX);
660 break;
661
662 default:
663 AssertMsgFailed(("Impossible!\n"));
664 return VERR_INTERNAL_ERROR;
665 }
666 }
667 else
668 rc = RTErrConvertFromErrno(errno);
669 }
670
671 LogFlow(("RTPathQueryInfo(%p:{%s}, pObjInfo=%p, %d): returns %Rrc\n",
672 pszPath, pszPath, pObjInfo, enmAdditionalAttribs, rc));
673 return rc;
674}
675
676
677RTR3DECL(int) RTPathSetTimes(const char *pszPath, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
678 PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
679{
680 /*
681 * Validate input.
682 */
683 AssertMsgReturn(VALID_PTR(pszPath), ("%p\n", pszPath), VERR_INVALID_POINTER);
684 AssertMsgReturn(*pszPath, ("%p\n", pszPath), VERR_INVALID_PARAMETER);
685 AssertMsgReturn(!pAccessTime || VALID_PTR(pAccessTime), ("%p\n", pAccessTime), VERR_INVALID_POINTER);
686 AssertMsgReturn(!pModificationTime || VALID_PTR(pModificationTime), ("%p\n", pModificationTime), VERR_INVALID_POINTER);
687 AssertMsgReturn(!pChangeTime || VALID_PTR(pChangeTime), ("%p\n", pChangeTime), VERR_INVALID_POINTER);
688 AssertMsgReturn(!pBirthTime || VALID_PTR(pBirthTime), ("%p\n", pBirthTime), VERR_INVALID_POINTER);
689
690 /*
691 * Convert the paths.
692 */
693 char *pszNativePath;
694 int rc = rtPathToNative(&pszNativePath, pszPath);
695 if (RT_SUCCESS(rc))
696 {
697 /*
698 * If it's a no-op, we'll only verify the existance of the file.
699 */
700 if (!pAccessTime && !pModificationTime)
701 {
702 struct stat Stat;
703 if (!stat(pszNativePath, &Stat))
704 rc = VINF_SUCCESS;
705 else
706 {
707 rc = RTErrConvertFromErrno(errno);
708 Log(("RTPathSetTimes('%s',,,,): failed with %Rrc and errno=%d\n", pszPath, rc, errno));
709 }
710 }
711 else
712 {
713 /*
714 * Convert the input to timeval, getting the missing one if necessary,
715 * and call the API which does the change.
716 */
717 struct timeval aTimevals[2];
718 if (pAccessTime && pModificationTime)
719 {
720 RTTimeSpecGetTimeval(pAccessTime, &aTimevals[0]);
721 RTTimeSpecGetTimeval(pModificationTime, &aTimevals[1]);
722 }
723 else
724 {
725 RTFSOBJINFO ObjInfo;
726 int rc = RTPathQueryInfo(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX);
727 if (RT_SUCCESS(rc))
728 {
729 RTTimeSpecGetTimeval(pAccessTime ? pAccessTime : &ObjInfo.AccessTime, &aTimevals[0]);
730 RTTimeSpecGetTimeval(pModificationTime ? pModificationTime : &ObjInfo.ModificationTime, &aTimevals[1]);
731 }
732 else
733 Log(("RTPathSetTimes('%s',%p,%p,,): RTPathQueryInfo failed with %Rrc\n",
734 pszPath, pAccessTime, pModificationTime, rc));
735 }
736 if (RT_SUCCESS(rc))
737 {
738 if (utimes(pszNativePath, aTimevals))
739 {
740 rc = RTErrConvertFromErrno(errno);
741 Log(("RTPathSetTimes('%s',%p,%p,,): failed with %Rrc and errno=%d\n",
742 pszPath, pAccessTime, pModificationTime, rc, errno));
743 }
744 }
745 }
746 }
747
748 LogFlow(("RTPathSetTimes(%p:{%s}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}, %p:{%RDtimespec}): return %Rrc\n",
749 pszPath, pszPath, pAccessTime, pAccessTime, pModificationTime, pModificationTime,
750 pChangeTime, pChangeTime, pBirthTime, pBirthTime));
751 return rc;
752}
753
754
755/**
756 * Checks if two files are the one and same file.
757 */
758static bool rtPathSame(const char *pszNativeSrc, const char *pszNativeDst)
759{
760 struct stat SrcStat;
761 if (stat(pszNativeSrc, &SrcStat))
762 return false;
763 struct stat DstStat;
764 if (stat(pszNativeDst, &DstStat))
765 return false;
766 Assert(SrcStat.st_dev && DstStat.st_dev);
767 Assert(SrcStat.st_ino && DstStat.st_ino);
768 if ( SrcStat.st_dev == DstStat.st_dev
769 && SrcStat.st_ino == DstStat.st_ino
770 && (SrcStat.st_mode & S_IFMT) == (DstStat.st_mode & S_IFMT))
771 return true;
772 return false;
773}
774
775
776/**
777 * Worker for RTPathRename, RTDirRename, RTFileRename.
778 *
779 * @returns IPRT status code.
780 * @param pszSrc The source path.
781 * @param pszDst The destintation path.
782 * @param fRename The rename flags.
783 * @param fFileType The filetype. We use the RTFMODE filetypes here. If it's 0,
784 * anything goes. If it's RTFS_TYPE_DIRECTORY we'll check that the
785 * source is a directory. If Its RTFS_TYPE_FILE we'll check that it's
786 * not a directory (we are NOT checking whether it's a file).
787 */
788int rtPathPosixRename(const char *pszSrc, const char *pszDst, unsigned fRename, RTFMODE fFileType)
789{
790 /*
791 * Convert the paths.
792 */
793 char *pszNativeSrc;
794 int rc = rtPathToNative(&pszNativeSrc, pszSrc);
795 if (RT_SUCCESS(rc))
796 {
797 char *pszNativeDst;
798 rc = rtPathToNative(&pszNativeDst, pszDst);
799 if (RT_SUCCESS(rc))
800 {
801 /*
802 * Check that the source exists and that any types that's specified matches.
803 * We have to check this first to avoid getting errnous VERR_ALREADY_EXISTS
804 * errors from the next step.
805 *
806 * There are race conditions here (perhaps unlikly ones but still), but I'm
807 * afraid there is little with can do to fix that.
808 */
809 struct stat SrcStat;
810 if (stat(pszNativeSrc, &SrcStat))
811 rc = RTErrConvertFromErrno(errno);
812 else if (!fFileType)
813 rc = VINF_SUCCESS;
814 else if (RTFS_IS_DIRECTORY(fFileType))
815 rc = S_ISDIR(SrcStat.st_mode) ? VINF_SUCCESS : VERR_NOT_A_DIRECTORY;
816 else
817 rc = S_ISDIR(SrcStat.st_mode) ? VERR_IS_A_DIRECTORY : VINF_SUCCESS;
818 if (RT_SUCCESS(rc))
819 {
820 bool fSameFile = false;
821
822 /*
823 * Check if the target exists, rename is rather destructive.
824 * We'll have to make sure we don't overwrite the source!
825 * Another race condition btw.
826 */
827 struct stat DstStat;
828 if (stat(pszNativeDst, &DstStat))
829 rc = errno == ENOENT ? VINF_SUCCESS : RTErrConvertFromErrno(errno);
830 else
831 {
832 Assert(SrcStat.st_dev && DstStat.st_dev);
833 Assert(SrcStat.st_ino && DstStat.st_ino);
834 if ( SrcStat.st_dev == DstStat.st_dev
835 && SrcStat.st_ino == DstStat.st_ino
836 && (SrcStat.st_mode & S_IFMT) == (SrcStat.st_mode & S_IFMT))
837 {
838 /*
839 * It's likely that we're talking about the same file here.
840 * We should probably check paths or whatever, but for now this'll have to be enough.
841 */
842 fSameFile = true;
843 }
844 if (fSameFile)
845 rc = VINF_SUCCESS;
846 else if (S_ISDIR(DstStat.st_mode) || !(fRename & RTPATHRENAME_FLAGS_REPLACE))
847 rc = VERR_ALREADY_EXISTS;
848 else
849 rc = VINF_SUCCESS;
850
851 }
852 if (RT_SUCCESS(rc))
853 {
854 if (!rename(pszNativeSrc, pszNativeDst))
855 rc = VINF_SUCCESS;
856 else if ( (fRename & RTPATHRENAME_FLAGS_REPLACE)
857 && (errno == ENOTDIR || errno == EEXIST))
858 {
859 /*
860 * Check that the destination isn't a directory.
861 * Yet another race condition.
862 */
863 if (rtPathSame(pszNativeSrc, pszNativeDst))
864 {
865 rc = VINF_SUCCESS;
866 Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): appears to be the same file... (errno=%d)\n",
867 pszSrc, pszDst, fRename, fFileType, errno));
868 }
869 else
870 {
871 if (stat(pszNativeDst, &DstStat))
872 rc = errno != ENOENT ? RTErrConvertFromErrno(errno) : VINF_SUCCESS;
873 else if (S_ISDIR(DstStat.st_mode))
874 rc = VERR_ALREADY_EXISTS;
875 else
876 rc = VINF_SUCCESS;
877 if (RT_SUCCESS(rc))
878 {
879 if (!unlink(pszNativeDst))
880 {
881 if (!rename(pszNativeSrc, pszNativeDst))
882 rc = VINF_SUCCESS;
883 else
884 {
885 rc = RTErrConvertFromErrno(errno);
886 Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n",
887 pszSrc, pszDst, fRename, fFileType, rc, errno));
888 }
889 }
890 else
891 {
892 rc = RTErrConvertFromErrno(errno);
893 Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): failed to unlink dst rc=%Rrc errno=%d\n",
894 pszSrc, pszDst, fRename, fFileType, rc, errno));
895 }
896 }
897 else
898 Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): dst !dir check failed rc=%Rrc\n",
899 pszSrc, pszDst, fRename, fFileType, rc));
900 }
901 }
902 else
903 {
904 rc = RTErrConvertFromErrno(errno);
905 if (errno == ENOTDIR)
906 rc = VERR_ALREADY_EXISTS; /* unless somebody is racing us, this is the right interpretation */
907 Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): rename failed rc=%Rrc errno=%d\n",
908 pszSrc, pszDst, fRename, fFileType, rc, errno));
909 }
910 }
911 else
912 Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): destination check failed rc=%Rrc errno=%d\n",
913 pszSrc, pszDst, fRename, fFileType, rc, errno));
914 }
915 else
916 Log(("rtPathRename('%s', '%s', %#x ,%RTfmode): source type check failed rc=%Rrc errno=%d\n",
917 pszSrc, pszDst, fRename, fFileType, rc, errno));
918
919 rtPathFreeNative(pszNativeDst);
920 }
921 rtPathFreeNative(pszNativeSrc);
922 }
923 return rc;
924}
925
926
927RTR3DECL(int) RTPathRename(const char *pszSrc, const char *pszDst, unsigned fRename)
928{
929 /*
930 * Validate input.
931 */
932 AssertMsgReturn(VALID_PTR(pszSrc), ("%p\n", pszSrc), VERR_INVALID_POINTER);
933 AssertMsgReturn(VALID_PTR(pszDst), ("%p\n", pszDst), VERR_INVALID_POINTER);
934 AssertMsgReturn(*pszSrc, ("%p\n", pszSrc), VERR_INVALID_PARAMETER);
935 AssertMsgReturn(*pszDst, ("%p\n", pszDst), VERR_INVALID_PARAMETER);
936 AssertMsgReturn(!(fRename & ~RTPATHRENAME_FLAGS_REPLACE), ("%#x\n", fRename), VERR_INVALID_PARAMETER);
937
938 /*
939 * Hand it to the worker.
940 */
941 int rc = rtPathPosixRename(pszSrc, pszDst, fRename, 0);
942
943 Log(("RTPathRename(%p:{%s}, %p:{%s}, %#x): returns %Rrc\n", pszSrc, pszSrc, pszDst, pszDst, fRename, rc));
944 return rc;
945}
946
947
948RTDECL(bool) RTPathExists(const char *pszPath)
949{
950 /*
951 * Validate input.
952 */
953 AssertPtrReturn(pszPath, false);
954 AssertReturn(*pszPath, false);
955
956 /*
957 * Convert the path and check if it exists using stat().
958 */
959 char *pszNativePath;
960 int rc = rtPathToNative(&pszNativePath, pszPath);
961 if (RT_SUCCESS(rc))
962 {
963 struct stat Stat;
964 if (!stat(pszNativePath, &Stat))
965 rc = VINF_SUCCESS;
966 else
967 rc = VERR_GENERAL_FAILURE;
968 RTStrFree(pszNativePath);
969 }
970 return RT_SUCCESS(rc);
971}
972
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette