%% options copyright owner = Dirk Krause copyright year = 2015-xxxx license = bsd %% header /** @file dk4symlink.h Find symbolic link target. */ #ifndef DK4CONF_H_INCLUDED #include "dk4conf.h" #endif #ifndef DK4TYPES_H_INCLUDED #include "dk4types.h" #endif #ifndef DK4ERROR_H_INCLUDED #include "dk4error.h" #endif #ifdef __cplusplus extern "C" { #endif /** Find target for symbolic link. @param bptr Destination buffer pointer. @param szb Size of destination buffer (number of dkChar). @param fn File name of link. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if bptr or fn is NULL or szb is 0, - DK4_E_SYSTEM
with errno value in iDetails1 if the readlink() function failed on Linux/Unix, - DK4_E_BUFFER_TOO_SMALL
if the output buffer is too small, - DK4_E_NOT_SUPPORTED
if no function was found to read a symbolic link target, - DK4_E_NOT_FOUND
if the CreateFile() or DeviceIoControl() function failed on Windows. */ int dk4symlink_target(dkChar *bptr, size_t szb, const dkChar *fn, dk4_er_t *erp); #ifdef __cplusplus } #endif %% module #include "dk4symlink.h" #if DK4_ON_WINDOWS #ifndef WINDOWS_H_INCLUDED #include #define WINDOWS_H_INCLUDED 1 #endif #if DK4_HAVE_NTIFS_H #ifndef NTIFS_H_INCLUDED #include #define NTIFS_H_INCLUDED 1 #endif #endif #endif #if DK4_HAVE_UNISTD_H #ifndef UNISTD_H_INCLUDED #include #define UNISTD_H_INCLUDED 1 #endif #endif #if DK4_HAVE_ERRNO_H #ifndef ERRNO_H_INCLUDED #include #define ERRNO_H_INCLUDED 1 #endif #endif #include "dk4mpl.h" #include "dk4mem.h" #include "dk4strd.h" $!trace-include #if DK4_ON_WINDOWS #if !DK4_HAVE_NTIFS_H typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; #endif #if defined(FSCTL_GET_REPARSE_POINT) /** Size of reparse point information for a symlink. */ #define SYMLINK_INFO_SIZE ((8 * (DK4_MAX_PATH))+sizeof(REPARSE_DATA_BUFFER)) /** The above size aligned to 16 bytes. */ #define SYMLINK_INFO_ALIGNED (16 * (((SYMLINK_INFO_SIZE) / 16) + 1)) #ifdef MAXIMUM_REPARSE_DATA_BUFFER_SIZE #define SYMLINK_BUFFER_SIZE \ (MAXIMUM_REPARSE_DATA_BUFFER_SIZE + SYMLINK_INFO_ALIGNED) #endif #endif /** Copy path name from reparse point buffer to destination buffer. @param bptr Destination buffer. @param szb Destination buffer size (number of wchar_t). @param sptr Source pointer. @param offs Offset of path name in source (in bytes). @param length Length of path name (in bytes). @param erp Error report, may be NULL. @return 1 on success, 0 on error. */ static int dk4symlink_copy_string( wchar_t *bptr, size_t szb, const wchar_t *sptr, USHORT offs, USHORT length, dk4_er_t *erp ) { int back = 0; if ((length / sizeof(wchar_t)) < szb) { DK4_MEMCPY(bptr,&(sptr[offs / sizeof(wchar_t)]),length); bptr[length / sizeof(wchar_t)] = L'\0'; back = 1; } else { dk4error_set_simple_error_code(erp, DK4_E_BUFFER_TOO_SMALL); } return back; } #endif int dk4symlink_target(dkChar *bptr, size_t szb, const dkChar *fn, dk4_er_t *erp) { #if DK4_ON_WINDOWS #if defined(FSCTL_GET_REPARSE_POINT) char repdabuf[SYMLINK_BUFFER_SIZE]; REPARSE_DATA_BUFFER *rdb; #endif HANDLE fh; DWORD dwres; BOOL result; #else ssize_t res; #endif int back = 0; $? "+ dk4symlink_target" if ((NULL != bptr) && (NULL != fn) && (0 < szb)) { *bptr = dkT('\0'); #if DK4_ON_WINDOWS #if DK4_CHAR_SIZE > 1 #if DK4_HAVE_GETFINALPATHNAMEBYHANDLE fh = CreateFileW( fn, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); if (INVALID_HANDLE_VALUE != fh) { dwres = GetFinalPathNameByHandleW(fh, bptr, (DWORD)szb, 0); if (0 < dwres) { if (dwres < (DWORD)szb) { back = 1; } } CloseHandle(fh); } #else $? ". no GetFinalPathNameByHandle" #endif if (0 == back) { #if defined(FSCTL_GET_REPARSE_POINT) /* !!!!! Second attempt with legacy procedure */ $? ". legacy procedure" fh = CreateFileW( fn, FILE_READ_ATTRIBUTES, (FILE_SHARE_READ | FILE_SHARE_WRITE), NULL, OPEN_EXISTING, ( FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT | FILE_ATTRIBUTE_REPARSE_POINT ), NULL ); if (INVALID_HANDLE_VALUE != fh) { dwres = 0; $? ". CreateFileW" result = DeviceIoControl( fh, FSCTL_GET_REPARSE_POINT, NULL, (DWORD)0U, (void *)repdabuf, (DWORD)sizeof(repdabuf), &dwres, NULL ); if (result) { $? ". DeviceIoControl" rdb = (REPARSE_DATA_BUFFER *)repdabuf; if (IO_REPARSE_TAG_MOUNT_POINT == rdb->ReparseTag) { $? ". mount point" back = dk4symlink_copy_string( bptr, szb, &(rdb->MountPointReparseBuffer.PathBuffer[0]), rdb->MountPointReparseBuffer.SubstituteNameOffset, rdb->MountPointReparseBuffer.SubstituteNameLength, erp ); } else { if (IO_REPARSE_TAG_SYMLINK == rdb->ReparseTag) { $? ". symbolic link" back = dk4symlink_copy_string( bptr, szb, &(rdb->SymbolicLinkReparseBuffer.PathBuffer[0]), rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset, rdb->SymbolicLinkReparseBuffer.SubstituteNameLength, erp ); } else { /* !!!!! Not mount point, not symlink */ $? "! neither mount point nor symbolic link" } } } else { $? "! DeviceIoControl" dk4error_set_simple_error_code(erp, DK4_E_NOT_FOUND); } CloseHandle(fh); } else { $? "! CreateFile" dk4error_set_simple_error_code(erp, DK4_E_NOT_FOUND); } #else dk4error_set_simple_error_code(erp, DK4_E_NOT_SUPPORTED); #endif } #else #if DK4_HAVE_GETFINALPATHNAMEBYHANDLE fh = CreateFileA( fn, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL ); if (INVALID_HANDLE_VALUE != fh) { dwres = GetFinalPathNameByHandleA(fh, bptr, (DWORD)szb, 0); if (0 < dwres) { if (dwres < (DWORD)szb) { back = 1; } } CloseHandle(fh); } #else /* Legacy procedure only available for wchar_t. */ dk4error_set_simple_error_code(erp, DK4_E_NOT_SUPPORTED); #endif #endif if (0 < back) { if (dkT('\\') == bptr[0]) { if ((dkT('\\') == bptr[1]) || (dkT('?') == bptr[1])) { if (dkT('?') == bptr[2]) { if (dkT('\\') == bptr[3]) { if (260 > dk4str_len(&(bptr[4]))) { dk4str_cpy_to_left(bptr, &(bptr[4])); } } } } } } #else #if DK4_CHAR_SIZE > 1 dk4error_set_simple_error_code(erp, DK4_E_NOT_SUPPORTED); #else errno = 0; res = readlink(fn, bptr, szb); if (0 < res) { if (res < szb) { bptr[res] = dkT('\0'); back = 1; } else { $? "! readlink requires larger buffer" bptr[0] = dkT('\0'); dk4error_set_simple_error_code(erp, DK4_E_BUFFER_TOO_SMALL); } } else { $? "! readlink <= 0" bptr[0] = dkT('\0'); if (0 > res) { dk4error_set_idetails(erp, DK4_E_SYSTEM, errno); } } #endif #endif if (0 == back) { *bptr = dkT('\0'); } } else { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); } $? "- dk4symlink_target %d", back return back; }