%% 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;
}