VirtualBox

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

Last change on this file since 5999 was 5999, checked in by vboxsync, 17 years ago

The Giant CDDL Dual-License Header Change.

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