452 lines
13 KiB
C++
452 lines
13 KiB
C++
/* fhandler_procsys.cc: fhandler for native NT namespace.
|
|
|
|
Copyright 2010, 2011, 2012, 2013 Red Hat, Inc.
|
|
|
|
This file is part of Cygwin.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
#include "winsup.h"
|
|
#include <stdlib.h>
|
|
#include "cygerrno.h"
|
|
#include "security.h"
|
|
#include "path.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include <winioctl.h>
|
|
#include "ntdll.h"
|
|
#include "tls_pbuf.h"
|
|
|
|
#include <dirent.h>
|
|
|
|
/* Path of the /proc/sys filesystem */
|
|
const char procsys[] = "/proc/sys";
|
|
const size_t procsys_len = sizeof (procsys) - 1;
|
|
|
|
#define mk_unicode_path(p) \
|
|
WCHAR namebuf[strlen (get_name ()) + 1]; \
|
|
{ \
|
|
const char *from; \
|
|
PWCHAR to; \
|
|
for (to = namebuf, from = get_name () + procsys_len; *from; \
|
|
to++, from++) \
|
|
/* The NT device namespace is ASCII only. */ \
|
|
*to = (*from == '/') ? L'\\' : (WCHAR) *from; \
|
|
if (to == namebuf) \
|
|
*to++ = L'\\'; \
|
|
*to = L'\0'; \
|
|
RtlInitUnicodeString ((p), namebuf); \
|
|
}
|
|
|
|
/* Returns 0 if path doesn't exist, >0 if path is a directory,
|
|
-1 if path is a file, -2 if it's a symlink. */
|
|
virtual_ftype_t
|
|
fhandler_procsys::exists (struct stat *buf)
|
|
{
|
|
UNICODE_STRING path;
|
|
UNICODE_STRING dir;
|
|
OBJECT_ATTRIBUTES attr;
|
|
IO_STATUS_BLOCK io;
|
|
NTSTATUS status;
|
|
HANDLE h;
|
|
FILE_BASIC_INFORMATION fbi;
|
|
bool internal = false;
|
|
bool desperate_parent_check = false;
|
|
/* Default device type is character device. */
|
|
virtual_ftype_t file_type = virt_chr;
|
|
|
|
if (strlen (get_name ()) == procsys_len)
|
|
return virt_rootdir;
|
|
mk_unicode_path (&path);
|
|
|
|
/* Try to open parent dir. If it works, the object is definitely
|
|
an object within the internal namespace. We don't need to test
|
|
it for being a file or dir on the filesystem anymore. If the
|
|
error is STATUS_OBJECT_TYPE_MISMATCH, we know that the file
|
|
itself is external. Otherwise we don't know. */
|
|
RtlSplitUnicodePath (&path, &dir, NULL);
|
|
/* RtlSplitUnicodePath preserves the trailing backslash in dir. Don't
|
|
preserve it to open the dir, unless it's the object root. */
|
|
if (dir.Length > sizeof (WCHAR))
|
|
dir.Length -= sizeof (WCHAR);
|
|
InitializeObjectAttributes (&attr, &dir, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
status = NtOpenDirectoryObject (&h, DIRECTORY_QUERY, &attr);
|
|
debug_printf ("NtOpenDirectoryObject: %y", status);
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
internal = true;
|
|
NtClose (h);
|
|
}
|
|
|
|
/* First check if the object is a symlink. */
|
|
InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
status = NtOpenSymbolicLinkObject (&h, READ_CONTROL | SYMBOLIC_LINK_QUERY,
|
|
&attr);
|
|
debug_printf ("NtOpenSymbolicLinkObject: %y", status);
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
/* If requested, check permissions. */
|
|
if (buf)
|
|
get_object_attribute (h, &buf->st_uid, &buf->st_gid, &buf->st_mode);
|
|
NtClose (h);
|
|
return virt_symlink;
|
|
}
|
|
else if (status == STATUS_ACCESS_DENIED)
|
|
return virt_symlink;
|
|
/* Then check if it's an object directory. */
|
|
status = NtOpenDirectoryObject (&h, READ_CONTROL | DIRECTORY_QUERY, &attr);
|
|
debug_printf ("NtOpenDirectoryObject: %y", status);
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
/* If requested, check permissions. */
|
|
if (buf)
|
|
get_object_attribute (h, &buf->st_uid, &buf->st_gid, &buf->st_mode);
|
|
NtClose (h);
|
|
return virt_directory;
|
|
}
|
|
else if (status == STATUS_ACCESS_DENIED)
|
|
return virt_directory;
|
|
/* Next try to open as file/device. */
|
|
status = NtOpenFile (&h, READ_CONTROL | FILE_READ_ATTRIBUTES, &attr, &io,
|
|
FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_BACKUP_INTENT);
|
|
debug_printf ("NtOpenFile: %y", status);
|
|
/* Name is invalid, that's nothing. */
|
|
if (status == STATUS_OBJECT_NAME_INVALID)
|
|
return virt_none;
|
|
/* If no media is found, or we get this dreaded sharing violation, let
|
|
the caller immediately try again as normal file. */
|
|
if (status == STATUS_NO_MEDIA_IN_DEVICE
|
|
|| status == STATUS_SHARING_VIOLATION)
|
|
return virt_fsfile; /* Just try again as normal file. */
|
|
/* If file or path can't be found, let caller try again as normal file. */
|
|
if (status == STATUS_OBJECT_PATH_NOT_FOUND
|
|
|| status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
return virt_fsfile;
|
|
/* Check for pipe errors, which make a good hint... */
|
|
if (status >= STATUS_PIPE_NOT_AVAILABLE && status <= STATUS_PIPE_BUSY)
|
|
return virt_pipe;
|
|
if (status == STATUS_ACCESS_DENIED && !internal)
|
|
{
|
|
/* Check if this is just some file or dir on a real FS to circumvent
|
|
most permission problems. Don't try that on internal objects,
|
|
since NtQueryAttributesFile might crash the machine if the underlying
|
|
driver is badly written. */
|
|
status = NtQueryAttributesFile (&attr, &fbi);
|
|
debug_printf ("NtQueryAttributesFile: %y", status);
|
|
if (NT_SUCCESS (status))
|
|
return (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
? virt_fsdir : virt_fsfile;
|
|
/* Ok, so we're desperate and the file still maybe on some filesystem.
|
|
To check this, we now split the path until we can finally access any
|
|
of the parent's. Then we fall through to check the parent type. In
|
|
contrast to the first parent check, we now check explicitely with
|
|
trailing backslash. This will fail for directories in the internal
|
|
namespace, so we won't accidentally test those. */
|
|
dir = path;
|
|
InitializeObjectAttributes (&attr, &dir, OBJ_CASE_INSENSITIVE,
|
|
NULL, NULL);
|
|
do
|
|
{
|
|
RtlSplitUnicodePath (&dir, &dir, NULL);
|
|
status = NtOpenFile (&h, READ_CONTROL | FILE_READ_ATTRIBUTES,
|
|
&attr, &io, FILE_SHARE_VALID_FLAGS,
|
|
FILE_OPEN_FOR_BACKUP_INTENT);
|
|
debug_printf ("NtOpenDirectoryObject: %y", status);
|
|
if (dir.Length > sizeof (WCHAR))
|
|
dir.Length -= sizeof (WCHAR);
|
|
}
|
|
while (dir.Length > sizeof (WCHAR) && !NT_SUCCESS (status));
|
|
desperate_parent_check = true;
|
|
}
|
|
if (NT_SUCCESS (status))
|
|
{
|
|
FILE_FS_DEVICE_INFORMATION ffdi;
|
|
|
|
/* If requested, check permissions. If this is a parent handle from
|
|
the above desperate parent check, skip. */
|
|
if (buf && !desperate_parent_check)
|
|
get_object_attribute (h, &buf->st_uid, &buf->st_gid, &buf->st_mode);
|
|
|
|
/* Check for the device type. */
|
|
status = NtQueryVolumeInformationFile (h, &io, &ffdi, sizeof ffdi,
|
|
FileFsDeviceInformation);
|
|
debug_printf ("NtQueryVolumeInformationFile: %y", status);
|
|
/* Don't call NtQueryInformationFile unless we know it's a safe type.
|
|
The call is known to crash machines, if the underlying driver is
|
|
badly written. */
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
NtClose (h);
|
|
return file_type;
|
|
}
|
|
if (ffdi.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM)
|
|
file_type = virt_blk;
|
|
else if (ffdi.DeviceType == FILE_DEVICE_NAMED_PIPE)
|
|
file_type = internal ? virt_blk : virt_pipe;
|
|
else if (ffdi.DeviceType == FILE_DEVICE_DISK
|
|
|| ffdi.DeviceType == FILE_DEVICE_CD_ROM
|
|
|| ffdi.DeviceType == FILE_DEVICE_DFS
|
|
|| ffdi.DeviceType == FILE_DEVICE_VIRTUAL_DISK)
|
|
{
|
|
/* Check for file attributes. If we get them, we peeked
|
|
into a real FS through /proc/sys. */
|
|
status = NtQueryInformationFile (h, &io, &fbi, sizeof fbi,
|
|
FileBasicInformation);
|
|
debug_printf ("NtQueryInformationFile: %y", status);
|
|
if (!NT_SUCCESS (status))
|
|
file_type = virt_blk;
|
|
else
|
|
file_type = (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
? virt_fsdir : virt_fsfile;
|
|
}
|
|
NtClose (h);
|
|
}
|
|
/* That's it. Return type we found above. */
|
|
return file_type;
|
|
}
|
|
|
|
virtual_ftype_t
|
|
fhandler_procsys::exists ()
|
|
{
|
|
return exists (NULL);
|
|
}
|
|
|
|
fhandler_procsys::fhandler_procsys ():
|
|
fhandler_virtual ()
|
|
{
|
|
}
|
|
|
|
#define UNREADABLE_SYMLINK_CONTENT "<access denied>"
|
|
|
|
bool
|
|
fhandler_procsys::fill_filebuf ()
|
|
{
|
|
char *fnamep;
|
|
UNICODE_STRING path, target;
|
|
OBJECT_ATTRIBUTES attr;
|
|
NTSTATUS status;
|
|
HANDLE h;
|
|
tmp_pathbuf tp;
|
|
size_t len;
|
|
|
|
mk_unicode_path (&path);
|
|
if (path.Buffer[path.Length / sizeof (WCHAR) - 1] == L'\\')
|
|
path.Length -= sizeof (WCHAR);
|
|
InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
status = NtOpenSymbolicLinkObject (&h, SYMBOLIC_LINK_QUERY, &attr);
|
|
if (!NT_SUCCESS (status))
|
|
goto unreadable;
|
|
RtlInitEmptyUnicodeString (&target, tp.w_get (),
|
|
(NT_MAX_PATH - 1) * sizeof (WCHAR));
|
|
status = NtQuerySymbolicLinkObject (h, &target, NULL);
|
|
NtClose (h);
|
|
if (!NT_SUCCESS (status))
|
|
goto unreadable;
|
|
len = sys_wcstombs (NULL, 0, target.Buffer, target.Length / sizeof (WCHAR));
|
|
filebuf = (char *) crealloc_abort (filebuf, procsys_len + len + 1);
|
|
sys_wcstombs (fnamep = stpcpy (filebuf, procsys), len + 1, target.Buffer,
|
|
target.Length / sizeof (WCHAR));
|
|
while ((fnamep = strchr (fnamep, '\\')))
|
|
*fnamep = '/';
|
|
return true;
|
|
|
|
unreadable:
|
|
filebuf = (char *) crealloc_abort (filebuf,
|
|
sizeof (UNREADABLE_SYMLINK_CONTENT));
|
|
strcpy (filebuf, UNREADABLE_SYMLINK_CONTENT);
|
|
return false;
|
|
}
|
|
|
|
int __reg2
|
|
fhandler_procsys::fstat (struct stat *buf)
|
|
{
|
|
const char *path = get_name ();
|
|
debug_printf ("fstat (%s)", path);
|
|
|
|
fhandler_base::fstat (buf);
|
|
/* Best bet. */
|
|
buf->st_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
|
|
buf->st_uid = 544;
|
|
buf->st_gid = 18;
|
|
buf->st_dev = buf->st_rdev = dev ();
|
|
buf->st_ino = get_ino ();
|
|
switch (exists (buf))
|
|
{
|
|
case virt_directory:
|
|
case virt_rootdir:
|
|
case virt_fsdir:
|
|
buf->st_mode |= S_IFDIR;
|
|
if (buf->st_mode & S_IRUSR)
|
|
buf->st_mode |= S_IXUSR;
|
|
if (buf->st_mode & S_IRGRP)
|
|
buf->st_mode |= S_IXGRP;
|
|
if (buf->st_mode & S_IROTH)
|
|
buf->st_mode |= S_IXOTH;
|
|
break;
|
|
case virt_file:
|
|
case virt_fsfile:
|
|
buf->st_mode |= S_IFREG;
|
|
break;
|
|
case virt_symlink:
|
|
buf->st_mode |= S_IFLNK;
|
|
break;
|
|
case virt_pipe:
|
|
buf->st_mode |= S_IFIFO;
|
|
break;
|
|
case virt_socket:
|
|
buf->st_mode |= S_IFSOCK;
|
|
break;
|
|
case virt_chr:
|
|
buf->st_mode |= S_IFCHR;
|
|
break;
|
|
case virt_blk:
|
|
buf->st_mode |= S_IFBLK;
|
|
break;
|
|
default:
|
|
set_errno (ENOENT);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
DIR *
|
|
fhandler_procsys::opendir (int fd)
|
|
{
|
|
UNICODE_STRING path;
|
|
OBJECT_ATTRIBUTES attr;
|
|
NTSTATUS status;
|
|
HANDLE h;
|
|
DIR *dir;
|
|
|
|
mk_unicode_path (&path);
|
|
InitializeObjectAttributes (&attr, &path, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
status = NtOpenDirectoryObject (&h, DIRECTORY_QUERY, &attr);
|
|
if (!NT_SUCCESS (status))
|
|
{
|
|
__seterrno_from_nt_status (status);
|
|
return NULL;
|
|
}
|
|
if (!(dir = fhandler_virtual::opendir (fd)))
|
|
NtClose (h);
|
|
else
|
|
dir->__handle = h;
|
|
return dir;
|
|
}
|
|
|
|
int
|
|
fhandler_procsys::readdir (DIR *dir, dirent *de)
|
|
{
|
|
NTSTATUS status;
|
|
struct fdbi
|
|
{
|
|
DIRECTORY_BASIC_INFORMATION dbi;
|
|
WCHAR buf[2][NAME_MAX + 1];
|
|
} f;
|
|
int res = EBADF;
|
|
|
|
if (dir->__handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
BOOLEAN restart = dir->__d_position ? FALSE : TRUE;
|
|
status = NtQueryDirectoryObject (dir->__handle, &f, sizeof f, TRUE,
|
|
restart, (PULONG) &dir->__d_position,
|
|
NULL);
|
|
if (!NT_SUCCESS (status))
|
|
res = ENMFILE;
|
|
else
|
|
{
|
|
sys_wcstombs (de->d_name, NAME_MAX + 1, f.dbi.ObjectName.Buffer,
|
|
f.dbi.ObjectName.Length / sizeof (WCHAR));
|
|
de->d_ino = hash_path_name (get_ino (), de->d_name);
|
|
de->d_type = 0;
|
|
res = 0;
|
|
}
|
|
}
|
|
syscall_printf ("%d = readdir(%p, %p)", res, dir, de);
|
|
return res;
|
|
}
|
|
|
|
long
|
|
fhandler_procsys::telldir (DIR *dir)
|
|
{
|
|
return dir->__d_position;
|
|
}
|
|
|
|
void
|
|
fhandler_procsys::seekdir (DIR *dir, long pos)
|
|
{
|
|
dir->__d_position = pos;
|
|
}
|
|
|
|
int
|
|
fhandler_procsys::closedir (DIR *dir)
|
|
{
|
|
if (dir->__handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
NtClose (dir->__handle);
|
|
dir->__handle = INVALID_HANDLE_VALUE;
|
|
}
|
|
return fhandler_virtual::closedir (dir);
|
|
}
|
|
|
|
void __reg3
|
|
fhandler_procsys::read (void *ptr, size_t& len)
|
|
{
|
|
fhandler_base::raw_read (ptr, len);
|
|
}
|
|
|
|
ssize_t __stdcall
|
|
fhandler_procsys::write (const void *ptr, size_t len)
|
|
{
|
|
return fhandler_base::raw_write (ptr, len);
|
|
}
|
|
|
|
int
|
|
fhandler_procsys::open (int flags, mode_t mode)
|
|
{
|
|
int res = 0;
|
|
|
|
if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
|
|
set_errno (EINVAL);
|
|
else
|
|
{
|
|
switch (exists (NULL))
|
|
{
|
|
case virt_directory:
|
|
case virt_rootdir:
|
|
if ((flags & O_ACCMODE) != O_RDONLY)
|
|
set_errno (EISDIR);
|
|
else
|
|
{
|
|
nohandle (true);
|
|
res = 1;
|
|
}
|
|
break;
|
|
case virt_none:
|
|
set_errno (ENOENT);
|
|
break;
|
|
default:
|
|
res = fhandler_base::open (flags, mode);
|
|
break;
|
|
}
|
|
}
|
|
syscall_printf ("%d = fhandler_procsys::open(%p, 0%o)", res, flags, mode);
|
|
return res;
|
|
}
|
|
|
|
int
|
|
fhandler_procsys::close ()
|
|
{
|
|
if (!nohandle ())
|
|
NtClose (get_handle ());
|
|
return fhandler_virtual::close ();
|
|
}
|
|
#if 0
|
|
int
|
|
fhandler_procsys::ioctl (unsigned int cmd, void *)
|
|
{
|
|
}
|
|
#endif
|