nt_native 0.1.2

Windows Native subsystem bindings for the Rust programming language.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
use core::mem;

use ntapi::ntioapi::{
    NtCreateFile, FILE_COMPLETE_IF_OPLOCKED, FILE_CREATE, FILE_CREATE_TREE_CONNECTION, FILE_DELETE_ON_CLOSE, FILE_DIRECTORY_FILE,
    FILE_NON_DIRECTORY_FILE, FILE_NO_EA_KNOWLEDGE, FILE_NO_INTERMEDIATE_BUFFERING, FILE_OPEN, FILE_OPENED, FILE_OPEN_BY_FILE_ID,
    FILE_OPEN_FOR_BACKUP_INTENT, FILE_OPEN_IF, FILE_OPEN_REPARSE_POINT, FILE_OPEN_REQUIRING_OPLOCK, FILE_OVERWRITE, FILE_OVERWRITE_IF,
    FILE_OVERWRITTEN, FILE_RANDOM_ACCESS, FILE_RESERVE_OPFILTER, FILE_SEQUENTIAL_ONLY, FILE_SUPERSEDE, FILE_SYNCHRONOUS_IO_ALERT,
    FILE_SYNCHRONOUS_IO_NONALERT, FILE_WRITE_THROUGH, IO_STATUS_BLOCK,
};
use ntapi::ntobapi::OBJ_INHERIT;
use winapi::shared::ntdef::{
    InitializeObjectAttributes, HANDLE, OBJ_CASE_INSENSITIVE, OBJ_DONT_REPARSE, OBJ_EXCLUSIVE, OBJ_FORCE_ACCESS_CHECK,
    OBJ_IGNORE_IMPERSONATED_DEVICEMAP, OBJ_KERNEL_HANDLE, OBJ_OPENIF, OBJ_OPENLINK, OBJ_PERMANENT, OBJ_VALID_ATTRIBUTES, PLARGE_INTEGER,
    PVOID,
};
use winapi::um::winnt::{
    FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, FILE_APPEND_DATA, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE,
    FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_EA, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM,
    FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_PINNED,
    FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, FILE_ATTRIBUTE_RECALL_ON_OPEN, FILE_ATTRIBUTE_REPARSE_POINT,
    FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, FILE_ATTRIBUTE_VIRTUAL,
    FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_LIST_DIRECTORY, FILE_READ_ATTRIBUTES, FILE_READ_DATA, FILE_READ_EA, FILE_SHARE_DELETE,
    FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES, FILE_WRITE_DATA, FILE_WRITE_EA, PSECURITY_DESCRIPTOR, READ_CONTROL,
    SECURITY_DESCRIPTOR, SYNCHRONIZE, WRITE_DAC, WRITE_OWNER,
};

use crate::{Handle, NtString, NullSafePtr, Result, ToUnicodeString};

bitflags! {
    /// Wrapper around [File Access Rights Constants](https://docs.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants)
    pub struct Access: u32 {
        const DELETE = winapi::um::winnt::DELETE;

        const READ_DATA       = FILE_READ_DATA;
        const READ_ATTRIBUTES = FILE_READ_ATTRIBUTES;
        const READ_EA         = FILE_READ_EA;
        const READ_CONTROL    = READ_CONTROL;

        const WRITE_DATA       = FILE_WRITE_DATA;
        const WRITE_ATTRIBUTES = FILE_WRITE_ATTRIBUTES;
        const WRITE_EA         = FILE_WRITE_EA;
        const WRITE_DAC        = WRITE_DAC;
        const WRITE_OWNER      = WRITE_OWNER;
        const APPEND_DATA      = FILE_APPEND_DATA;
        const SYNCHRONIZE      = SYNCHRONIZE;

        // Directory specials
        const LIST_DIR = FILE_LIST_DIRECTORY;
        const ADD_FILE = FILE_ADD_FILE;
        const ADD_DIR  = FILE_ADD_SUBDIRECTORY;

        // GENERIC
        const GENERIC_READ  = FILE_GENERIC_READ;
        const GENERIC_WRITE = FILE_GENERIC_WRITE;
    }
}

impl Default for Access {
    fn default() -> Access {
        Access::GENERIC_READ | Access::GENERIC_WRITE
    }
}

bitflags! {
    pub struct Attribute: u32 {
        const INHERIT                         = OBJ_INHERIT;
        const PERMANENT                       = OBJ_PERMANENT;
        const EXCLUSIVE                       = OBJ_EXCLUSIVE;
        const CASE_INSENSITIVE                = OBJ_CASE_INSENSITIVE;
        const OPENIF                          = OBJ_OPENIF;
        const OPENLINK                        = OBJ_OPENLINK;
        const KERNEL_HANDLE                   = OBJ_KERNEL_HANDLE;
        const FORCE_ACCESS_CHECK              = OBJ_FORCE_ACCESS_CHECK;
        const IGNORE_IMPERSONATED_DEVICEMAP   = OBJ_IGNORE_IMPERSONATED_DEVICEMAP;
        const DONT_REPARSE                    = OBJ_DONT_REPARSE;
        const VALID_ATTRIBUTES                = OBJ_VALID_ATTRIBUTES;
    }
}

impl Default for Attribute {
    fn default() -> Attribute {
        Attribute::CASE_INSENSITIVE
    }
}

bitflags! {
    /// Wrapper around [File Attribute Constants](https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants)
    pub struct FileAttribute: u32 {
        const READONLY              = FILE_ATTRIBUTE_READONLY;
        const HIDDEN                = FILE_ATTRIBUTE_HIDDEN;
        const SYSTEM                = FILE_ATTRIBUTE_SYSTEM;
        const DIRECTORY             = FILE_ATTRIBUTE_DIRECTORY;
        const ARCHIVE               = FILE_ATTRIBUTE_ARCHIVE;
        const DEVICE                = FILE_ATTRIBUTE_DEVICE;
        const NORMAL                = FILE_ATTRIBUTE_NORMAL;
        const TEMPORARY             = FILE_ATTRIBUTE_TEMPORARY;
        const SPARSE_FILE           = FILE_ATTRIBUTE_SPARSE_FILE;
        const REPARSE_POINT         = FILE_ATTRIBUTE_REPARSE_POINT;
        const COMPRESSED            = FILE_ATTRIBUTE_COMPRESSED;
        const OFFLINE               = FILE_ATTRIBUTE_OFFLINE;
        const NOT_CONTENT_INDEXED   = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
        const ENCRYPTED             = FILE_ATTRIBUTE_ENCRYPTED;
        const INTEGRITY_STREAM      = FILE_ATTRIBUTE_INTEGRITY_STREAM;
        const VIRTUAL               = FILE_ATTRIBUTE_VIRTUAL;
        const NO_SCRUB_DATA         = FILE_ATTRIBUTE_NO_SCRUB_DATA;
        const EA                    = FILE_ATTRIBUTE_EA;
        const PINNED                = FILE_ATTRIBUTE_PINNED;
        const UNPINNED              = FILE_ATTRIBUTE_UNPINNED;
        const RECALL_ON_OPEN        = FILE_ATTRIBUTE_RECALL_ON_OPEN;
        const RECALL_ON_DATA_ACCESS = FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS;
    }
}

impl Default for FileAttribute {
    fn default() -> FileAttribute {
        FileAttribute::NORMAL
    }
}

bitflags! {
    pub struct ShareAccess: u32 {
        const READ   = FILE_SHARE_READ;
        const WRITE  = FILE_SHARE_WRITE;
        const DELETE = FILE_SHARE_DELETE;
    }
}

impl Default for ShareAccess {
    fn default() -> ShareAccess {
        ShareAccess::READ | ShareAccess::WRITE
    }
}

pub struct SecurityDescriptor(SECURITY_DESCRIPTOR);

pub enum CreateDisposition {
    /// If the file already exists, replace it with the given file.
    /// If it does not, create the given file.
    Supersede,

    /// If the file already exists, fail the request and do not create or open the given file.
    /// If it does not, create the given file.
    Create,

    /// If the file already exists, open it instead of creating a new file.
    /// If it does not, fail the request and do not create a new file.
    Open,

    /// If the file already exists, open it.
    /// If it does not, create the given file.
    OpenOrCreate,

    /// If the file already exists, open it and overwrite it.
    /// If it does not, fail the request.
    Overwrite,

    /// If the file already exists, open it and overwrite it.
    /// If it does not, create the given file.
    OverwriteOrCreate,
}

impl CreateDisposition {
    pub fn to_u32(&self) -> u32 {
        match self {
            CreateDisposition::Supersede => FILE_SUPERSEDE,
            CreateDisposition::Create => FILE_CREATE,
            CreateDisposition::Open => FILE_OPEN,
            CreateDisposition::OpenOrCreate => FILE_OPEN_IF,
            CreateDisposition::Overwrite => FILE_OVERWRITE,
            CreateDisposition::OverwriteOrCreate => FILE_OVERWRITE_IF,
        }
    }
}

bitflags! {
    pub struct Options: u32 {
        const DIRECTORY              = FILE_DIRECTORY_FILE;
        const NON_DIRECTORY          = FILE_NON_DIRECTORY_FILE;
        const WRITE_THROUGH          = FILE_WRITE_THROUGH;
        const SEQUENTIAL_ONLY        = FILE_SEQUENTIAL_ONLY;
        const RANDOM_ACCESS          = FILE_RANDOM_ACCESS;
        const NO_BUFFERING           = FILE_NO_INTERMEDIATE_BUFFERING;
        const SYNC_ALERT             = FILE_SYNCHRONOUS_IO_ALERT;
        const SYNC_NONALERT          = FILE_SYNCHRONOUS_IO_NONALERT;
        const CREATE_TREE_CONNECTION = FILE_CREATE_TREE_CONNECTION;
        const NO_EA_KNOWLEDGE        = FILE_NO_EA_KNOWLEDGE;
        const OPEN_REPARSE_POINT     = FILE_OPEN_REPARSE_POINT;
        const DELETE_ON_CLOSE        = FILE_DELETE_ON_CLOSE;
        const OPEN_BY_FILE_ID        = FILE_OPEN_BY_FILE_ID;
        const OPEN_FOR_BACKUP        = FILE_OPEN_FOR_BACKUP_INTENT;
        const RESERVE_OPFILTER       = FILE_RESERVE_OPFILTER;
        const OPEN_REQUIRING_OPLOCK  = FILE_OPEN_REQUIRING_OPLOCK;
        const COMPLETE_IF_OPLOCKED   = FILE_COMPLETE_IF_OPLOCKED;
    }
}

impl Default for Options {
    fn default() -> Options {
        // FILE_SYNCHRONOUS_IO_NONALERT is almost always mandatory!
        // see https://github.com/reactos/reactos/blob/893a3c9d030fd8b078cbd747eeefd3f6ce57e560/dll/win32/kernel32/client/file/create.c#L128
        Options::SYNC_NONALERT
    }
}

pub struct NewHandle {
    pub access: Access,
    pub attributes: Attribute,
    pub security_descriptor: Option<SecurityDescriptor>,
    pub root: Option<Handle>,
    pub allocation_size: u64,
    pub file_attributes: FileAttribute,
    pub share_access: ShareAccess,
    pub create_disposition: CreateDisposition,
    pub options: Options,
    pub ea: crate::Vec<u8>,
}

impl Default for NewHandle {
    fn default() -> Self {
        Self {
            access: Access::default(),
            attributes: Attribute::default(),
            security_descriptor: None,
            root: None,
            allocation_size: 0,
            file_attributes: FileAttribute::default(),
            share_access: ShareAccess::default(),
            create_disposition: CreateDisposition::OpenOrCreate,
            options: Options::default(),
            ea: crate::Vec::new(),
        }
    }
}

// public API
impl NewHandle {
    pub fn with_cd(create_disposition: CreateDisposition) -> Self {
        Self {
            create_disposition,
            ..Self::default()
        }
    }

    pub fn create_new(name: &NtString) -> Result<Handle> {
        let (handle, _) = Self::with_cd(CreateDisposition::Create).build(name)?;
        Ok(handle)
    }

    pub fn open(name: &NtString) -> Result<Handle> {
        let (handle, _) = Self::with_cd(CreateDisposition::Open).build(name)?;
        Ok(handle)
    }

    pub fn open_readonly(name: &NtString) -> Result<Handle> {
        let (handle, _) = Self::with_cd(CreateDisposition::Open).build(name)?;

        Ok(handle)
    }

    pub fn open_or_create(name: &NtString) -> Result<(Handle, bool)> {
        Self::with_cd(CreateDisposition::OpenOrCreate).build(name)
    }

    pub fn owerwrite(name: &NtString) -> Result<Handle> {
        let (handle, _) = Self::with_cd(CreateDisposition::Overwrite).build(name)?;
        Ok(handle)
    }

    pub fn owerwrite_or_create(name: &NtString) -> Result<(Handle, bool)> {
        Self::with_cd(CreateDisposition::OverwriteOrCreate).build(name)
    }

    pub fn build(self, dos_name: &NtString) -> Result<(Handle, bool)> {
        let (nt_name, this) = self.auto_options(dos_name)?;

        this.build_nt(&nt_name)
    }

    pub fn build_nt(mut self, nt_name: &impl ToUnicodeString) -> Result<(Handle, bool)> {
        if self.options.contains(Options::DELETE_ON_CLOSE) {
            self.access |= Access::DELETE;
        }

        let root: HANDLE = match &self.root {
            None => core::ptr::null_mut(),
            Some(r) => r.as_raw(),
        };

        let security_descriptor: PSECURITY_DESCRIPTOR = match &self.security_descriptor {
            None => core::ptr::null_mut(),
            Some(sd) => &sd.0 as *const _ as PSECURITY_DESCRIPTOR,
        };

        unsafe {
            let mut oa = mem::zeroed();
            let mut unicode_str = nt_name.to_unicode_string();
            InitializeObjectAttributes(&mut oa, &mut unicode_str, self.attributes.bits, root, security_descriptor);

            let mut raw: HANDLE = mem::zeroed();
            let mut iosb: IO_STATUS_BLOCK = mem::zeroed();
            let status = NtCreateFile(
                &mut raw,
                self.access.bits,
                &mut oa,
                &mut iosb,
                self.allocation_size_ptr(),
                self.file_attributes.bits,
                self.share_access.bits,
                self.create_disposition.to_u32(),
                self.options.bits,
                self.ea.safe_ptr() as PVOID,
                self.ea.len() as u32,
            );

            nt_result!(status, {
                let already_exists = match self.create_disposition {
                    CreateDisposition::OpenOrCreate => iosb.Information == (FILE_OPENED as usize),
                    CreateDisposition::OverwriteOrCreate => iosb.Information == (FILE_OVERWRITTEN as usize),
                    _ => false,
                };

                (Handle::new(raw), already_exists)
            })
        }
    }
}

// internals
impl NewHandle {
    pub(crate) fn device(access: Access) -> Self {
        Self {
            access,
            share_access: ShareAccess::READ | ShareAccess::WRITE,
            create_disposition: CreateDisposition::Open,
            file_attributes: FileAttribute::NORMAL,
            ..NewHandle::default()
        }
    }

    pub(crate) fn auto_options(mut self, dos_name: &NtString) -> Result<(NtString, Self)> {
        let (nt_name, is_dir) = super::dos_name_to_nt(dos_name)?;
        if !self.options.contains(Options::DIRECTORY) && !self.options.contains(Options::NON_DIRECTORY) {
            // autodetect only if not set manually
            if is_dir {
                self.options.remove(Options::NON_DIRECTORY);
                self.options |= Options::DIRECTORY;
            } else {
                self.options.remove(Options::DIRECTORY);
                self.options |= Options::NON_DIRECTORY;
            }
        }

        Ok((nt_name, self))
    }

    unsafe fn allocation_size_ptr(&self) -> PLARGE_INTEGER {
        match self.allocation_size {
            0 => core::ptr::null_mut(),
            _ => &self.allocation_size as *const _ as PLARGE_INTEGER,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn access_smoke() {
        let read = Access::GENERIC_READ;
        assert!(read.contains(Access::READ_CONTROL));
        assert!(read.contains(Access::READ_DATA));
        assert!(read.contains(Access::READ_ATTRIBUTES));
        assert!(read.contains(Access::READ_EA));
        assert!(read.contains(Access::SYNCHRONIZE));

        let access = Access::READ_CONTROL
            | Access::WRITE_DATA
            | Access::WRITE_ATTRIBUTES
            | Access::WRITE_EA
            | Access::APPEND_DATA
            | Access::SYNCHRONIZE;
        assert_eq!(access, Access::GENERIC_WRITE);

        let def = Access::default();
        assert!(def.contains(Access::WRITE_DATA));
        assert!(def.contains(Access::WRITE_ATTRIBUTES));
        assert!(def.contains(Access::READ_ATTRIBUTES));
        assert!(def.contains(Access::READ_DATA));
    }

    #[test]
    fn auto_options() {
        let (_, builder) = NewHandle::default().auto_options(&nt_str!("dir.name/file.name")).unwrap();
        assert!(builder.options.contains(Options::NON_DIRECTORY)); // autodetected by name

        let (_, builder) = NewHandle::default().auto_options(&nt_str!("dir.name/")).unwrap();
        assert!(builder.options.contains(Options::DIRECTORY)); // autodetected by name

        let (_, builder) = NewHandle {
            options: Options::NON_DIRECTORY,
            ..Default::default()
        }
        .auto_options(&nt_str!("dir.name/"))
        .unwrap();
        assert!(builder.options.contains(Options::NON_DIRECTORY)); // name ignored!

        let (_, builder) = NewHandle {
            options: Options::DIRECTORY,
            ..Default::default()
        }
        .auto_options(&nt_str!("dir.name/file.name"))
        .unwrap();
        assert!(builder.options.contains(Options::DIRECTORY)); // name ignored!
    }
}