Browse Source

完善pipe系统调用以及openat系统调用 (#441)

LoGin 1 year ago
parent
commit
0fb515b011

+ 1 - 0
kernel/src/arch/x86_64/syscall.rs

@@ -23,6 +23,7 @@ pub const SYS_FCHMOD: usize = 91;
 pub const SYS_UMASK: usize = 95;
 pub const SYS_SYSINFO: usize = 99;
 pub const SYS_CLOCK_GETTIME: usize = 228;
+pub const SYS_OPENAT: usize = 257;
 pub const SYS_FCHMODAT: usize = 268;
 pub const SYS_FACCESSAT: usize = 269;
 pub const SYS_PRLIMIT64: usize = 302;

+ 2 - 2
kernel/src/filesystem/vfs/core.rs

@@ -218,7 +218,7 @@ pub fn do_remove_dir(dirfd: i32, path: &str) -> Result<u64, SystemError> {
     }
 
     let pcb = ProcessManager::current_pcb();
-    let (inode_begin, remain_path) = user_path_at(&pcb, dirfd, path.to_string())?;
+    let (inode_begin, remain_path) = user_path_at(&pcb, dirfd, path)?;
 
     let inode: Result<Arc<dyn IndexNode>, SystemError> =
         inode_begin.lookup_follow_symlink(remain_path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES);
@@ -258,7 +258,7 @@ pub fn do_unlink_at(dirfd: i32, path: &str) -> Result<u64, SystemError> {
         return Err(SystemError::ENAMETOOLONG);
     }
     let pcb = ProcessManager::current_pcb();
-    let (inode_begin, remain_path) = user_path_at(&pcb, dirfd, path.to_string())?;
+    let (inode_begin, remain_path) = user_path_at(&pcb, dirfd, path)?;
 
     let inode: Result<Arc<dyn IndexNode>, SystemError> =
         inode_begin.lookup_follow_symlink(&remain_path, VFS_MAX_FOLLOW_SYMLINK_TIMES);

+ 43 - 39
kernel/src/filesystem/vfs/file.rs

@@ -46,45 +46,49 @@ bitflags! {
     /// 与Linux 5.19.10的uapi/asm-generic/fcntl.h相同
     /// https://opengrok.ringotek.cn/xref/linux-5.19.10/tools/include/uapi/asm-generic/fcntl.h#19
     pub struct FileMode: u32{
-    /* File access modes for `open' and `fcntl'.  */
-    /// Open Read-only
-    const O_RDONLY = 0o0;
-    /// Open Write-only
-    const O_WRONLY = 0o1;
-    /// Open read/write
-    const O_RDWR = 0o2;
-    /// Mask for file access modes
-    const O_ACCMODE = 0o00000003;
-
-    /* Bits OR'd into the second argument to open.  */
-    /// Create file if it does not exist
-    const O_CREAT = 0o00000100;
-    /// Fail if file already exists
-    const O_EXCL = 0o00000200;
-    /// Do not assign controlling terminal
-    const O_NOCTTY = 0o00000400;
-    /// 文件存在且是普通文件,并以O_RDWR或O_WRONLY打开,则它会被清空
-    const O_TRUNC = 0o00001000;
-    /// 文件指针会被移动到文件末尾
-    const O_APPEND = 0o00002000;
-    /// 非阻塞式IO模式
-    const O_NONBLOCK = 0o00004000;
-    /// 每次write都等待物理I/O完成,但是如果写操作不影响读取刚写入的数据,则不等待文件属性更新
-    const O_DSYNC = 0o00010000;
-    /// fcntl, for BSD compatibility
-    const FASYNC = 0o00020000;
-    /* direct disk access hint */
-    const O_DIRECT = 0o00040000;
-    const O_LARGEFILE = 0o00100000;
-    /// 打开的必须是一个目录
-    const O_DIRECTORY = 0o00200000;
-    /// Do not follow symbolic links
-    const O_NOFOLLOW = 0o00400000;
-    const O_NOATIME = 0o01000000;
-    /// set close_on_exec
-    const O_CLOEXEC = 0o02000000;
-    /// 每次write都等到物理I/O完成,包括write引起的文件属性的更新
-    const O_SYNC = 0o04000000;
+        /* File access modes for `open' and `fcntl'.  */
+        /// Open Read-only
+        const O_RDONLY = 0o0;
+        /// Open Write-only
+        const O_WRONLY = 0o1;
+        /// Open read/write
+        const O_RDWR = 0o2;
+        /// Mask for file access modes
+        const O_ACCMODE = 0o00000003;
+
+        /* Bits OR'd into the second argument to open.  */
+        /// Create file if it does not exist
+        const O_CREAT = 0o00000100;
+        /// Fail if file already exists
+        const O_EXCL = 0o00000200;
+        /// Do not assign controlling terminal
+        const O_NOCTTY = 0o00000400;
+        /// 文件存在且是普通文件,并以O_RDWR或O_WRONLY打开,则它会被清空
+        const O_TRUNC = 0o00001000;
+        /// 文件指针会被移动到文件末尾
+        const O_APPEND = 0o00002000;
+        /// 非阻塞式IO模式
+        const O_NONBLOCK = 0o00004000;
+        /// 每次write都等待物理I/O完成,但是如果写操作不影响读取刚写入的数据,则不等待文件属性更新
+        const O_DSYNC = 0o00010000;
+        /// fcntl, for BSD compatibility
+        const FASYNC = 0o00020000;
+        /* direct disk access hint */
+        const O_DIRECT = 0o00040000;
+        const O_LARGEFILE = 0o00100000;
+        /// 打开的必须是一个目录
+        const O_DIRECTORY = 0o00200000;
+        /// Do not follow symbolic links
+        const O_NOFOLLOW = 0o00400000;
+        const O_NOATIME = 0o01000000;
+        /// set close_on_exec
+        const O_CLOEXEC = 0o02000000;
+        /// 每次write都等到物理I/O完成,包括write引起的文件属性的更新
+        const O_SYNC = 0o04000000;
+
+        const O_PATH = 0o10000000;
+
+        const O_PATH_FLAGS = Self::O_DIRECTORY.bits|Self::O_NOFOLLOW.bits|Self::O_CLOEXEC.bits|Self::O_PATH.bits;
     }
 }
 

+ 100 - 4
kernel/src/filesystem/vfs/open.rs

@@ -1,11 +1,17 @@
+use alloc::sync::Arc;
+
 use crate::{
+    driver::base::block::SeekFrom,
     process::ProcessManager,
     syscall::{user_access::check_and_clone_cstr, SystemError},
 };
 
 use super::{
-    fcntl::AtFlags, syscall::ModeType, utils::user_path_at, MAX_PATHLEN,
-    VFS_MAX_FOLLOW_SYMLINK_TIMES,
+    fcntl::AtFlags,
+    file::{File, FileMode},
+    syscall::{ModeType, OpenHow, OpenHowResolve},
+    utils::{rsplit_path, user_path_at},
+    FileType, IndexNode, MAX_PATHLEN, ROOT_INODE, VFS_MAX_FOLLOW_SYMLINK_TIMES,
 };
 
 pub(super) fn do_faccessat(
@@ -34,7 +40,7 @@ pub(super) fn do_faccessat(
         return Err(SystemError::EINVAL);
     }
 
-    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;
+    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, &path)?;
 
     // 如果找不到文件,则返回错误码ENOENT
     let _inode = inode.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?;
@@ -50,7 +56,7 @@ pub fn do_fchmodat(dirfd: i32, path: *const u8, _mode: ModeType) -> Result<usize
         return Err(SystemError::EINVAL);
     }
 
-    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;
+    let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, &path)?;
 
     // 如果找不到文件,则返回错误码ENOENT
     let _inode = inode.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?;
@@ -60,3 +66,93 @@ pub fn do_fchmodat(dirfd: i32, path: *const u8, _mode: ModeType) -> Result<usize
 
     return Ok(0);
 }
+
+pub(super) fn do_sys_open(
+    dfd: i32,
+    path: &str,
+    o_flags: FileMode,
+    mode: ModeType,
+    follow_symlink: bool,
+) -> Result<usize, SystemError> {
+    let how = OpenHow::new(o_flags, mode, OpenHowResolve::empty());
+    return do_sys_openat2(dfd, path, how, follow_symlink);
+}
+
+fn do_sys_openat2(
+    dirfd: i32,
+    path: &str,
+    how: OpenHow,
+    follow_symlink: bool,
+) -> Result<usize, SystemError> {
+    // kdebug!("open: path: {}, mode: {:?}", path, mode);
+    // 文件名过长
+    if path.len() > MAX_PATHLEN as usize {
+        return Err(SystemError::ENAMETOOLONG);
+    }
+    let (inode_begin, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;
+    let inode: Result<Arc<dyn IndexNode>, SystemError> = inode_begin.lookup_follow_symlink(
+        &path,
+        if follow_symlink {
+            VFS_MAX_FOLLOW_SYMLINK_TIMES
+        } else {
+            0
+        },
+    );
+
+    let inode: Arc<dyn IndexNode> = if inode.is_err() {
+        let errno = inode.unwrap_err();
+        // 文件不存在,且需要创建
+        if how.o_flags.contains(FileMode::O_CREAT)
+            && !how.o_flags.contains(FileMode::O_DIRECTORY)
+            && errno == SystemError::ENOENT
+        {
+            let (filename, parent_path) = rsplit_path(&path);
+            // 查找父目录
+            let parent_inode: Arc<dyn IndexNode> =
+                ROOT_INODE().lookup(parent_path.unwrap_or("/"))?;
+            // 创建文件
+            let inode: Arc<dyn IndexNode> = parent_inode.create(
+                filename,
+                FileType::File,
+                ModeType::from_bits_truncate(0o755),
+            )?;
+            inode
+        } else {
+            // 不需要创建文件,因此返回错误码
+            return Err(errno);
+        }
+    } else {
+        inode.unwrap()
+    };
+
+    let file_type: FileType = inode.metadata()?.file_type;
+    // 如果要打开的是文件夹,而目标不是文件夹
+    if how.o_flags.contains(FileMode::O_DIRECTORY) && file_type != FileType::Dir {
+        return Err(SystemError::ENOTDIR);
+    }
+
+    // 创建文件对象
+
+    let mut file: File = File::new(inode, how.o_flags)?;
+
+    // 打开模式为“追加”
+    if how.o_flags.contains(FileMode::O_APPEND) {
+        file.lseek(SeekFrom::SeekEnd(0))?;
+    }
+
+    // 如果O_TRUNC,并且,打开模式包含O_RDWR或O_WRONLY,清空文件
+    if how.o_flags.contains(FileMode::O_TRUNC)
+        && (how.o_flags.contains(FileMode::O_RDWR) || how.o_flags.contains(FileMode::O_WRONLY))
+        && file_type == FileType::File
+    {
+        file.ftruncate(0)?;
+    }
+    // 把文件对象存入pcb
+    let r = ProcessManager::current_pcb()
+        .fd_table()
+        .write()
+        .alloc_fd(file, None)
+        .map(|fd| fd as usize);
+
+    return r;
+}

+ 112 - 73
kernel/src/filesystem/vfs/syscall.rs

@@ -25,7 +25,7 @@ use super::{
     core::{do_mkdir, do_remove_dir, do_unlink_at},
     fcntl::{AtFlags, FcntlCommand, FD_CLOEXEC},
     file::{File, FileMode},
-    open::{do_faccessat, do_fchmodat},
+    open::{do_faccessat, do_fchmodat, do_sys_open},
     utils::{rsplit_path, user_path_at},
     Dirent, FileType, IndexNode, MAX_PATHLEN, ROOT_INODE, VFS_MAX_FOLLOW_SYMLINK_TIMES,
 };
@@ -146,85 +146,124 @@ impl PosixKstat {
         }
     }
 }
-impl Syscall {
-    /// @brief 为当前进程打开一个文件
-    ///
-    /// @param path 文件路径
-    /// @param o_flags 打开文件的标志位
-    ///
-    /// @return 文件描述符编号,或者是错误码
-    pub fn open(path: &str, mode: FileMode, follow_symlink: bool) -> Result<usize, SystemError> {
-        // kdebug!("open: path: {}, mode: {:?}", path, mode);
-        // 文件名过长
-        if path.len() > MAX_PATHLEN as usize {
-            return Err(SystemError::ENAMETOOLONG);
-        }
-
-        let inode: Result<Arc<dyn IndexNode>, SystemError> = ROOT_INODE().lookup_follow_symlink(
-            path,
-            if follow_symlink {
-                VFS_MAX_FOLLOW_SYMLINK_TIMES
-            } else {
-                0
-            },
-        );
 
-        let inode: Arc<dyn IndexNode> = if inode.is_err() {
-            let errno = inode.unwrap_err();
-            // 文件不存在,且需要创建
-            if mode.contains(FileMode::O_CREAT)
-                && !mode.contains(FileMode::O_DIRECTORY)
-                && errno == SystemError::ENOENT
-            {
-                let (filename, parent_path) = rsplit_path(path);
-                // 查找父目录
-                let parent_inode: Arc<dyn IndexNode> =
-                    ROOT_INODE().lookup(parent_path.unwrap_or("/"))?;
-                // 创建文件
-                let inode: Arc<dyn IndexNode> = parent_inode.create(
-                    filename,
-                    FileType::File,
-                    ModeType::from_bits_truncate(0o755),
-                )?;
-                inode
-            } else {
-                // 不需要创建文件,因此返回错误码
-                return Err(errno);
-            }
-        } else {
-            inode.unwrap()
-        };
+///
+///  Arguments for how openat2(2) should open the target path. If only @flags and
+///  @mode are non-zero, then openat2(2) operates very similarly to openat(2).
+///
+///  However, unlike openat(2), unknown or invalid bits in @flags result in
+///  -EINVAL rather than being silently ignored. @mode must be zero unless one of
+///  {O_CREAT, O_TMPFILE} are set.
+///
+/// ## 成员变量
+///
+/// - flags: O_* flags.
+/// - mode: O_CREAT/O_TMPFILE file mode.
+/// - resolve: RESOLVE_* flags.
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub struct PosixOpenHow {
+    pub flags: u64,
+    pub mode: u64,
+    pub resolve: u64,
+}
 
-        let file_type: FileType = inode.metadata()?.file_type;
-        // 如果要打开的是文件夹,而目标不是文件夹
-        if mode.contains(FileMode::O_DIRECTORY) && file_type != FileType::Dir {
-            return Err(SystemError::ENOTDIR);
+impl PosixOpenHow {
+    #[allow(dead_code)]
+    pub fn new(flags: u64, mode: u64, resolve: u64) -> Self {
+        Self {
+            flags,
+            mode,
+            resolve,
         }
+    }
+}
 
-        // 创建文件对象
+#[derive(Debug, Clone, Copy)]
+pub struct OpenHow {
+    pub o_flags: FileMode,
+    pub mode: ModeType,
+    pub resolve: OpenHowResolve,
+}
 
-        let mut file: File = File::new(inode, mode)?;
+impl OpenHow {
+    pub fn new(mut o_flags: FileMode, mut mode: ModeType, resolve: OpenHowResolve) -> Self {
+        if !o_flags.contains(FileMode::O_CREAT) {
+            mode = ModeType::empty();
+        }
 
-        // 打开模式为“追加”
-        if mode.contains(FileMode::O_APPEND) {
-            file.lseek(SeekFrom::SeekEnd(0))?;
+        if o_flags.contains(FileMode::O_PATH) {
+            o_flags = o_flags.intersection(FileMode::O_PATH_FLAGS);
         }
 
-        // 如果O_TRUNC,并且,打开模式包含O_RDWR或O_WRONLY,清空文件
-        if mode.contains(FileMode::O_TRUNC)
-            && (mode.contains(FileMode::O_RDWR) || mode.contains(FileMode::O_WRONLY))
-            && file_type == FileType::File
-        {
-            file.ftruncate(0)?;
+        Self {
+            o_flags,
+            mode,
+            resolve,
         }
-        // 把文件对象存入pcb
-        let r = ProcessManager::current_pcb()
-            .fd_table()
-            .write()
-            .alloc_fd(file, None)
-            .map(|fd| fd as usize);
+    }
+}
 
-        return r;
+impl From<PosixOpenHow> for OpenHow {
+    fn from(posix_open_how: PosixOpenHow) -> Self {
+        let o_flags = FileMode::from_bits_truncate(posix_open_how.flags as u32);
+        let mode = ModeType::from_bits_truncate(posix_open_how.mode as u32);
+        let resolve = OpenHowResolve::from_bits_truncate(posix_open_how.resolve as u64);
+        return Self::new(o_flags, mode, resolve);
+    }
+}
+
+bitflags! {
+    pub struct OpenHowResolve: u64{
+        /// Block mount-point crossings
+        ///     (including bind-mounts).
+        const RESOLVE_NO_XDEV = 0x01;
+
+        /// Block traversal through procfs-style
+        ///     "magic-links"
+        const RESOLVE_NO_MAGICLINKS = 0x02;
+
+        /// Block traversal through all symlinks
+        ///     (implies OEXT_NO_MAGICLINKS)
+        const RESOLVE_NO_SYMLINKS = 0x04;
+        /// Block "lexical" trickery like
+        ///     "..", symlinks, and absolute
+        const RESOLVE_BENEATH = 0x08;
+        /// Make all jumps to "/" and ".."
+        ///     be scoped inside the dirfd
+        ///     (similar to chroot(2)).
+        const RESOLVE_IN_ROOT = 0x10;
+        // Only complete if resolution can be
+        // 			completed through cached lookup. May
+        // 			return -EAGAIN if that's not
+        // 			possible.
+        const RESOLVE_CACHED = 0x20;
+    }
+}
+impl Syscall {
+    /// @brief 为当前进程打开一个文件
+    ///
+    /// @param path 文件路径
+    /// @param o_flags 打开文件的标志位
+    ///
+    /// @return 文件描述符编号,或者是错误码
+    pub fn open(
+        path: &str,
+        flags: FileMode,
+        mode: ModeType,
+        follow_symlink: bool,
+    ) -> Result<usize, SystemError> {
+        return do_sys_open(AtFlags::AT_FDCWD.bits(), path, flags, mode, follow_symlink);
+    }
+
+    pub fn openat(
+        dirfd: i32,
+        path: &str,
+        o_flags: FileMode,
+        mode: ModeType,
+        follow_symlink: bool,
+    ) -> Result<usize, SystemError> {
+        return do_sys_open(dirfd, path, o_flags, mode, follow_symlink);
     }
 
     /// @brief 关闭文件
@@ -764,14 +803,14 @@ impl Syscall {
     }
 
     pub fn stat(path: &str, user_kstat: *mut PosixKstat) -> Result<usize, SystemError> {
-        let fd = Self::open(path, FileMode::O_RDONLY, true)?;
+        let fd = Self::open(path, FileMode::O_RDONLY, ModeType::empty(), true)?;
         let r = Self::fstat(fd as i32, user_kstat);
         Self::close(fd).ok();
         return r;
     }
 
     pub fn lstat(path: &str, user_kstat: *mut PosixKstat) -> Result<usize, SystemError> {
-        let fd = Self::open(path, FileMode::O_RDONLY, false)?;
+        let fd = Self::open(path, FileMode::O_RDONLY, ModeType::empty(), false)?;
         let r = Self::fstat(fd as i32, user_kstat);
         Self::close(fd).ok();
         return r;
@@ -847,7 +886,7 @@ impl Syscall {
             return Err(SystemError::EINVAL);
         }
 
-        let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?;
+        let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, &path)?;
 
         let inode = inode.lookup(path.as_str())?;
         if inode.metadata()?.file_type != FileType::SymLink {

+ 8 - 4
kernel/src/filesystem/vfs/utils.rs

@@ -35,9 +35,10 @@ pub fn rsplit_path(path: &str) -> (&str, Option<&str>) {
 pub fn user_path_at(
     pcb: &Arc<ProcessControlBlock>,
     dirfd: i32,
-    mut path: String,
+    path: &str,
 ) -> Result<(Arc<dyn IndexNode>, String), SystemError> {
     let mut inode = ROOT_INODE();
+    let ret_path;
     // 如果path不是绝对路径,则需要拼接
     if path.as_bytes()[0] != b'/' {
         // 如果dirfd不是AT_FDCWD,则需要检查dirfd是否是目录
@@ -58,13 +59,16 @@ pub fn user_path_at(
             }
 
             inode = file_guard.inode();
+            ret_path = String::from(path);
         } else {
             let mut cwd = pcb.basic().cwd();
             cwd.push('/');
-            cwd.push_str(path.as_str());
-            path = cwd;
+            cwd.push_str(path);
+            ret_path = cwd;
         }
+    } else {
+        ret_path = String::from(path);
     }
 
-    return Ok((inode, path));
+    return Ok((inode, ret_path));
 }

+ 36 - 3
kernel/src/syscall/mod.rs

@@ -6,7 +6,8 @@ use core::{
 use crate::{
     arch::syscall::{
         SYS_ACCESS, SYS_CHMOD, SYS_CLOCK_GETTIME, SYS_FACCESSAT, SYS_FACCESSAT2, SYS_FCHMOD,
-        SYS_FCHMODAT, SYS_LSTAT, SYS_PRLIMIT64, SYS_READV, SYS_SYSINFO, SYS_UMASK, SYS_UNLINK,
+        SYS_FCHMODAT, SYS_LSTAT, SYS_OPENAT, SYS_PRLIMIT64, SYS_READV, SYS_SYSINFO, SYS_UMASK,
+        SYS_UNLINK,
     },
     libs::{futex::constant::FutexFlag, rand::GRandFlags},
     process::{
@@ -367,6 +368,7 @@ pub const SYS_RT_SIGRETURN: usize = 15;
 pub const SYS_IOCTL: usize = 16;
 
 pub const SYS_WRITEV: usize = 20;
+pub const SYS_PIPE: usize = 22;
 
 pub const SYS_MADVISE: usize = 28;
 
@@ -456,7 +458,7 @@ pub const SYS_READLINK_AT: usize = 267;
 
 pub const SYS_ACCEPT4: usize = 288;
 
-pub const SYS_PIPE: usize = 293;
+pub const SYS_PIPE2: usize = 293;
 
 #[allow(dead_code)]
 pub const SYS_GET_RANDOM: usize = 318;
@@ -515,8 +517,31 @@ impl Syscall {
                     let path: &str = path.unwrap();
 
                     let flags = args[1];
+                    let mode = args[2];
+
                     let open_flags: FileMode = FileMode::from_bits_truncate(flags as u32);
-                    Self::open(path, open_flags, true)
+                    let mode = ModeType::from_bits(mode as u32).ok_or(SystemError::EINVAL)?;
+                    Self::open(path, open_flags, mode, true)
+                };
+                res
+            }
+
+            SYS_OPENAT => {
+                let dirfd = args[0] as i32;
+                let path: &CStr = unsafe { CStr::from_ptr(args[1] as *const c_char) };
+                let flags = args[2];
+                let mode = args[3];
+
+                let path: Result<&str, core::str::Utf8Error> = path.to_str();
+                let res = if path.is_err() {
+                    Err(SystemError::EINVAL)
+                } else {
+                    let path: &str = path.unwrap();
+
+                    let open_flags: FileMode =
+                        FileMode::from_bits(flags as u32).ok_or(SystemError::EINVAL)?;
+                    let mode = ModeType::from_bits(mode as u32).ok_or(SystemError::EINVAL)?;
+                    Self::openat(dirfd, path, open_flags, mode, true)
                 };
                 res
             }
@@ -725,6 +750,14 @@ impl Syscall {
 
             SYS_CLOCK => Self::clock(),
             SYS_PIPE => {
+                let pipefd: *mut i32 = args[0] as *mut c_int;
+                if pipefd.is_null() {
+                    Err(SystemError::EFAULT)
+                } else {
+                    Self::pipe2(pipefd, FileMode::empty())
+                }
+            }
+            SYS_PIPE2 => {
                 let pipefd: *mut i32 = args[0] as *mut c_int;
                 let arg1 = args[1];
                 if pipefd.is_null() {