mirror of
https://github.com/ziglang/zig.git
synced 2026-02-07 06:57:13 +00:00
Merge branch 'rohlem-fix-GetFinalPathNameByHandle-before-win10_rs4'
Merges #7379
This commit is contained in:
commit
56c03881eb
@ -2436,3 +2436,46 @@ test "freeing empty string with null-terminated sentinel" {
|
||||
const empty_string = try dupeZ(testing.allocator, u8, "");
|
||||
testing.allocator.free(empty_string);
|
||||
}
|
||||
|
||||
/// Returns a slice with the given new alignment,
|
||||
/// all other pointer attributes copied from `AttributeSource`.
|
||||
fn AlignedSlice(comptime AttributeSource: type, comptime new_alignment: u29) type {
|
||||
const info = @typeInfo(AttributeSource).Pointer;
|
||||
return @Type(.{
|
||||
.Pointer = .{
|
||||
.size = .Slice,
|
||||
.is_const = info.is_const,
|
||||
.is_volatile = info.is_volatile,
|
||||
.is_allowzero = info.is_allowzero,
|
||||
.alignment = new_alignment,
|
||||
.child = info.child,
|
||||
.sentinel = null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the largest slice in the given bytes that conforms to the new alignment,
|
||||
/// or `null` if the given bytes contain no conforming address.
|
||||
pub fn alignInBytes(bytes: []u8, comptime new_alignment: usize) ?[]align(new_alignment) u8 {
|
||||
const begin_address = @ptrToInt(bytes.ptr);
|
||||
const end_address = begin_address + bytes.len;
|
||||
|
||||
const begin_address_aligned = mem.alignForward(begin_address, new_alignment);
|
||||
const new_length = std.math.sub(usize, end_address, begin_address_aligned) catch |e| switch (e) {
|
||||
error.Overflow => return null,
|
||||
};
|
||||
const alignment_offset = begin_address_aligned - begin_address;
|
||||
return @alignCast(new_alignment, bytes[alignment_offset .. alignment_offset + new_length]);
|
||||
}
|
||||
|
||||
/// Returns the largest sub-slice within the given slice that conforms to the new alignment,
|
||||
/// or `null` if the given slice contains no conforming address.
|
||||
pub fn alignInSlice(slice: anytype, comptime new_alignment: usize) ?AlignedSlice(@TypeOf(slice), new_alignment) {
|
||||
const bytes = sliceAsBytes(slice);
|
||||
const aligned_bytes = alignInBytes(bytes, new_alignment) orelse return null;
|
||||
|
||||
const Element = @TypeOf(slice[0]);
|
||||
const slice_length_bytes = aligned_bytes.len - (aligned_bytes.len % @sizeOf(Element));
|
||||
const aligned_slice = bytesAsSlice(Element, aligned_bytes[0..slice_length_bytes]);
|
||||
return @alignCast(new_alignment, aligned_slice);
|
||||
}
|
||||
|
||||
@ -953,7 +953,56 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
|
||||
return @bitCast(u64, result);
|
||||
}
|
||||
|
||||
pub fn QueryObjectName(
|
||||
handle: HANDLE,
|
||||
out_buffer: []u16,
|
||||
) ![]u16 {
|
||||
const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong;
|
||||
|
||||
const info = @ptrCast(*OBJECT_NAME_INFORMATION, out_buffer_aligned);
|
||||
//buffer size is specified in bytes
|
||||
const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) catch |e| switch (e) {
|
||||
error.Overflow => std.math.maxInt(ULONG),
|
||||
};
|
||||
//last argument would return the length required for full_buffer, not exposed here
|
||||
const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null);
|
||||
switch (rc) {
|
||||
.SUCCESS => {
|
||||
// info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0)
|
||||
// if the object was "unnamed", not sure if this can happen for file handles
|
||||
if (info.Name.MaximumLength == 0) return error.Unexpected;
|
||||
// resulting string length is specified in bytes
|
||||
const path_length_unterminated = @divExact(info.Name.Length, 2);
|
||||
return info.Name.Buffer[0..path_length_unterminated];
|
||||
},
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.INVALID_HANDLE => return error.InvalidHandle,
|
||||
// triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH),
|
||||
// or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL)
|
||||
.INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong,
|
||||
else => |e| return unexpectedStatus(e),
|
||||
}
|
||||
}
|
||||
test "QueryObjectName" {
|
||||
if (comptime builtin.os.tag != .windows)
|
||||
return;
|
||||
|
||||
//any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths.
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const handle = tmp.dir.fd;
|
||||
var out_buffer: [PATH_MAX_WIDE]u16 = undefined;
|
||||
|
||||
var result_path = try QueryObjectName(handle, &out_buffer);
|
||||
const required_len_in_u16 = result_path.len + @divExact(@ptrToInt(result_path.ptr) - @ptrToInt(&out_buffer), 2) + 1;
|
||||
//insufficient size
|
||||
std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1]));
|
||||
//exactly-sufficient size
|
||||
_ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]);
|
||||
}
|
||||
|
||||
pub const GetFinalPathNameByHandleError = error{
|
||||
AccessDenied,
|
||||
BadPathName,
|
||||
FileNotFound,
|
||||
NameTooLong,
|
||||
@ -981,32 +1030,31 @@ pub fn GetFinalPathNameByHandle(
|
||||
fmt: GetFinalPathNameByHandleFormat,
|
||||
out_buffer: []u16,
|
||||
) GetFinalPathNameByHandleError![]u16 {
|
||||
// Get normalized path; doesn't include volume name though.
|
||||
var path_buffer: [@sizeOf(FILE_NAME_INFORMATION) + PATH_MAX_WIDE * 2]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined;
|
||||
try QueryInformationFile(hFile, .FileNormalizedNameInformation, path_buffer[0..]);
|
||||
|
||||
// Get NT volume name.
|
||||
var volume_buffer: [@sizeOf(FILE_NAME_INFORMATION) + MAX_PATH]u8 align(@alignOf(FILE_NAME_INFORMATION)) = undefined; // MAX_PATH bytes should be enough since it's Windows-defined name
|
||||
try QueryInformationFile(hFile, .FileVolumeNameInformation, volume_buffer[0..]);
|
||||
|
||||
const file_name = @ptrCast(*const FILE_NAME_INFORMATION, &path_buffer[0]);
|
||||
const file_name_u16 = @ptrCast([*]const u16, &file_name.FileName[0])[0 .. file_name.FileNameLength / 2];
|
||||
|
||||
const volume_name = @ptrCast(*const FILE_NAME_INFORMATION, &volume_buffer[0]);
|
||||
const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) {
|
||||
// we assume InvalidHandle is close enough to FileNotFound in semantics
|
||||
// to not further complicate the error set
|
||||
error.InvalidHandle => return error.FileNotFound,
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
switch (fmt.volume_name) {
|
||||
.Nt => {
|
||||
// Nothing to do, we simply copy the bytes to the user-provided buffer.
|
||||
const volume_name_u16 = @ptrCast([*]const u16, &volume_name.FileName[0])[0 .. volume_name.FileNameLength / 2];
|
||||
|
||||
if (out_buffer.len < volume_name_u16.len + file_name_u16.len) return error.NameTooLong;
|
||||
|
||||
std.mem.copy(u16, out_buffer[0..], volume_name_u16);
|
||||
std.mem.copy(u16, out_buffer[volume_name_u16.len..], file_name_u16);
|
||||
|
||||
return out_buffer[0 .. volume_name_u16.len + file_name_u16.len];
|
||||
// the returned path is already in .Nt format
|
||||
return final_path;
|
||||
},
|
||||
.Dos => {
|
||||
// parse the string to separate volume path from file path
|
||||
const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\");
|
||||
|
||||
// TODO find out if a path can start with something besides `\Device\<volume name>`,
|
||||
// and if we need to handle it differently
|
||||
// (i.e. how to determine the start and end of the volume name in that case)
|
||||
if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected;
|
||||
|
||||
const file_path_begin_index = mem.indexOfPos(u16, final_path, expected_prefix.len, &[_]u16{'\\'}) orelse unreachable;
|
||||
const volume_name_u16 = final_path[0..file_path_begin_index];
|
||||
const file_name_u16 = final_path[file_path_begin_index..];
|
||||
|
||||
// Get DOS volume name. DOS volume names are actually symbolic link objects to the
|
||||
// actual NT volume. For example:
|
||||
// (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C:
|
||||
@ -1039,10 +1087,10 @@ pub fn GetFinalPathNameByHandle(
|
||||
|
||||
var input_struct = @ptrCast(*MOUNTMGR_MOUNT_POINT, &input_buf[0]);
|
||||
input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT);
|
||||
input_struct.DeviceNameLength = @intCast(USHORT, volume_name.FileNameLength);
|
||||
@memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, &volume_name.FileName[0]), volume_name.FileNameLength);
|
||||
input_struct.DeviceNameLength = @intCast(USHORT, volume_name_u16.len * 2);
|
||||
@memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..], @ptrCast([*]const u8, volume_name_u16.ptr), volume_name_u16.len * 2);
|
||||
|
||||
DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, input_buf[0..], output_buf[0..]) catch |err| switch (err) {
|
||||
DeviceIoControl(mgmt_handle, IOCTL_MOUNTMGR_QUERY_POINTS, &input_buf, &output_buf) catch |err| switch (err) {
|
||||
error.AccessDenied => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
@ -1062,22 +1110,20 @@ pub fn GetFinalPathNameByHandle(
|
||||
|
||||
// Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks
|
||||
// with traditional DOS drive letters, so pick the first one available.
|
||||
const prefix_u8 = "\\DosDevices\\";
|
||||
var prefix_buf_u16: [prefix_u8.len]u16 = undefined;
|
||||
const prefix_len_u16 = std.unicode.utf8ToUtf16Le(prefix_buf_u16[0..], prefix_u8[0..]) catch unreachable;
|
||||
const prefix = prefix_buf_u16[0..prefix_len_u16];
|
||||
var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\");
|
||||
const prefix = prefix_buf[0..prefix_buf.len];
|
||||
|
||||
if (std.mem.startsWith(u16, symlink, prefix)) {
|
||||
if (mem.startsWith(u16, symlink, prefix)) {
|
||||
const drive_letter = symlink[prefix.len..];
|
||||
|
||||
if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong;
|
||||
|
||||
std.mem.copy(u16, out_buffer[0..], drive_letter);
|
||||
std.mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
|
||||
mem.copy(u16, out_buffer, drive_letter);
|
||||
mem.copy(u16, out_buffer[drive_letter.len..], file_name_u16);
|
||||
const total_len = drive_letter.len + file_name_u16.len;
|
||||
|
||||
// Validate that DOS does not contain any spurious nul bytes.
|
||||
if (std.mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
|
||||
if (mem.indexOfScalar(u16, out_buffer[0..total_len], 0)) |_| {
|
||||
return error.BadPathName;
|
||||
}
|
||||
|
||||
@ -1092,6 +1138,30 @@ pub fn GetFinalPathNameByHandle(
|
||||
}
|
||||
}
|
||||
|
||||
test "GetFinalPathNameByHandle" {
|
||||
if (comptime builtin.os.tag != .windows)
|
||||
return;
|
||||
|
||||
//any file will do
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const handle = tmp.dir.fd;
|
||||
var buffer: [PATH_MAX_WIDE]u16 = undefined;
|
||||
|
||||
//check with sufficient size
|
||||
const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer);
|
||||
_ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer);
|
||||
|
||||
const required_len_in_u16 = nt_path.len + @divExact(@ptrToInt(nt_path.ptr) - @ptrToInt(&buffer), 2) + 1;
|
||||
//check with insufficient size
|
||||
std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1]));
|
||||
std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1]));
|
||||
|
||||
//check with exactly-sufficient size
|
||||
_ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]);
|
||||
_ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]);
|
||||
}
|
||||
|
||||
pub const QueryInformationFileError = error{Unexpected};
|
||||
|
||||
pub fn QueryInformationFile(
|
||||
|
||||
@ -1620,3 +1620,18 @@ pub const IOCTL_MOUNTMGR_QUERY_POINTS: ULONG = 0x6d0008;
|
||||
pub const SD_RECEIVE = 0;
|
||||
pub const SD_SEND = 1;
|
||||
pub const SD_BOTH = 2;
|
||||
|
||||
pub const OBJECT_INFORMATION_CLASS = extern enum {
|
||||
ObjectBasicInformation = 0,
|
||||
ObjectNameInformation = 1,
|
||||
ObjectTypeInformation = 2,
|
||||
ObjectTypesInformation = 3,
|
||||
ObjectHandleFlagInformation = 4,
|
||||
ObjectSessionInformation = 5,
|
||||
MaxObjectInfoClass,
|
||||
};
|
||||
|
||||
pub const OBJECT_NAME_INFORMATION = extern struct {
|
||||
Name: UNICODE_STRING,
|
||||
};
|
||||
pub const POBJECT_NAME_INFORMATION = *OBJECT_NAME_INFORMATION;
|
||||
|
||||
@ -113,3 +113,11 @@ pub extern "NtDll" fn NtWaitForKeyedEvent(
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
pub extern "NtDll" fn RtlSetCurrentDirectory_U(PathName: *UNICODE_STRING) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
pub extern "NtDll" fn NtQueryObject(
|
||||
Handle: HANDLE,
|
||||
ObjectInformationClass: OBJECT_INFORMATION_CLASS,
|
||||
ObjectInformation: PVOID,
|
||||
ObjectInformationLength: ULONG,
|
||||
ReturnLength: ?*ULONG,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
|
||||
@ -95,7 +95,7 @@ pub const Target = struct {
|
||||
win7 = 0x06010000,
|
||||
win8 = 0x06020000,
|
||||
win8_1 = 0x06030000,
|
||||
win10 = 0x0A000000,
|
||||
win10 = 0x0A000000, //aka win10_th1
|
||||
win10_th2 = 0x0A000001,
|
||||
win10_rs1 = 0x0A000002,
|
||||
win10_rs2 = 0x0A000003,
|
||||
@ -103,11 +103,34 @@ pub const Target = struct {
|
||||
win10_rs4 = 0x0A000005,
|
||||
win10_rs5 = 0x0A000006,
|
||||
win10_19h1 = 0x0A000007,
|
||||
win10_20h1 = 0x0A000008,
|
||||
win10_vb = 0x0A000008, //aka win10_19h2
|
||||
win10_mn = 0x0A000009, //aka win10_20h1
|
||||
win10_fe = 0x0A00000A, //aka win10_20h2
|
||||
_,
|
||||
|
||||
/// Latest Windows version that the Zig Standard Library is aware of
|
||||
pub const latest = WindowsVersion.win10_20h1;
|
||||
pub const latest = WindowsVersion.win10_fe;
|
||||
|
||||
/// Compared against build numbers reported by the runtime to distinguish win10 versions,
|
||||
/// where 0x0A000000 + index corresponds to the WindowsVersion u32 value.
|
||||
pub const known_win10_build_numbers = [_]u32{
|
||||
10240, //win10 aka win10_th1
|
||||
10586, //win10_th2
|
||||
14393, //win10_rs1
|
||||
15063, //win10_rs2
|
||||
16299, //win10_rs3
|
||||
17134, //win10_rs4
|
||||
17763, //win10_rs5
|
||||
18362, //win10_19h1
|
||||
18363, //win10_vb aka win10_19h2
|
||||
19041, //win10_mn aka win10_20h1
|
||||
19042, //win10_fe aka win10_20h2
|
||||
};
|
||||
|
||||
/// Returns whether the first version `self` is newer (greater) than or equal to the second version `ver`.
|
||||
pub fn isAtLeast(self: WindowsVersion, ver: WindowsVersion) bool {
|
||||
return @enumToInt(self) >= @enumToInt(ver);
|
||||
}
|
||||
|
||||
pub const Range = struct {
|
||||
min: WindowsVersion,
|
||||
|
||||
@ -14,6 +14,7 @@ const process = std.process;
|
||||
const Target = std.Target;
|
||||
const CrossTarget = std.zig.CrossTarget;
|
||||
const macos = @import("system/macos.zig");
|
||||
pub const windows = @import("system/windows.zig");
|
||||
|
||||
pub const getSDKPath = macos.getSDKPath;
|
||||
|
||||
@ -249,43 +250,9 @@ pub const NativeTargetInfo = struct {
|
||||
}
|
||||
},
|
||||
.windows => {
|
||||
var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined;
|
||||
version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info));
|
||||
|
||||
switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) {
|
||||
.SUCCESS => {},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
// Starting from the system infos build a NTDDI-like version
|
||||
// constant whose format is:
|
||||
// B0 B1 B2 B3
|
||||
// `---` `` ``--> Sub-version (Starting from Windows 10 onwards)
|
||||
// \ `--> Service pack (Always zero in the constants defined)
|
||||
// `--> OS version (Major & minor)
|
||||
const os_ver: u16 = @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 |
|
||||
@intCast(u16, version_info.dwMinorVersion & 0xff);
|
||||
const sp_ver: u8 = 0;
|
||||
const sub_ver: u8 = if (os_ver >= 0x0A00) subver: {
|
||||
// There's no other way to obtain this info beside
|
||||
// checking the build number against a known set of
|
||||
// values
|
||||
const known_build_numbers = [_]u32{
|
||||
10240, 10586, 14393, 15063, 16299, 17134, 17763,
|
||||
18362, 19041,
|
||||
};
|
||||
var last_idx: usize = 0;
|
||||
for (known_build_numbers) |build, i| {
|
||||
if (version_info.dwBuildNumber >= build)
|
||||
last_idx = i;
|
||||
}
|
||||
break :subver @truncate(u8, last_idx);
|
||||
} else 0;
|
||||
|
||||
const version: u32 = @as(u32, os_ver) << 16 | @as(u32, sp_ver) << 8 | sub_ver;
|
||||
|
||||
os.version_range.windows.max = @intToEnum(Target.Os.WindowsVersion, version);
|
||||
os.version_range.windows.min = @intToEnum(Target.Os.WindowsVersion, version);
|
||||
const detected_version = windows.detectRuntimeVersion();
|
||||
os.version_range.windows.min = detected_version;
|
||||
os.version_range.windows.max = detected_version;
|
||||
},
|
||||
.macos => {
|
||||
var scbuf: [32]u8 = undefined;
|
||||
|
||||
45
lib/std/zig/system/windows.zig
Normal file
45
lib/std/zig/system/windows.zig
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2015-2020 Zig Contributors
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.
|
||||
const std = @import("std");
|
||||
|
||||
pub const WindowsVersion = std.Target.Os.WindowsVersion;
|
||||
|
||||
/// Returns the highest known WindowsVersion deduced from reported runtime information.
|
||||
/// Discards information about in-between versions we don't differentiate.
|
||||
pub fn detectRuntimeVersion() WindowsVersion {
|
||||
var version_info: std.os.windows.RTL_OSVERSIONINFOW = undefined;
|
||||
version_info.dwOSVersionInfoSize = @sizeOf(@TypeOf(version_info));
|
||||
|
||||
switch (std.os.windows.ntdll.RtlGetVersion(&version_info)) {
|
||||
.SUCCESS => {},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
// Starting from the system infos build a NTDDI-like version
|
||||
// constant whose format is:
|
||||
// B0 B1 B2 B3
|
||||
// `---` `` ``--> Sub-version (Starting from Windows 10 onwards)
|
||||
// \ `--> Service pack (Always zero in the constants defined)
|
||||
// `--> OS version (Major & minor)
|
||||
const os_ver: u16 = @intCast(u16, version_info.dwMajorVersion & 0xff) << 8 |
|
||||
@intCast(u16, version_info.dwMinorVersion & 0xff);
|
||||
const sp_ver: u8 = 0;
|
||||
const sub_ver: u8 = if (os_ver >= 0x0A00) subver: {
|
||||
// There's no other way to obtain this info beside
|
||||
// checking the build number against a known set of
|
||||
// values
|
||||
var last_idx: usize = 0;
|
||||
for (WindowsVersion.known_win10_build_numbers) |build, i| {
|
||||
if (version_info.dwBuildNumber >= build)
|
||||
last_idx = i;
|
||||
}
|
||||
break :subver @truncate(u8, last_idx);
|
||||
} else 0;
|
||||
|
||||
const version: u32 = @as(u32, os_ver) << 16 | @as(u16, sp_ver) << 8 | sub_ver;
|
||||
|
||||
return @intToEnum(WindowsVersion, version);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user