Specifications source code for LSB Core 3.1

/*
 * Copyright (c) 2005-2006 Institute for System Programming
 * Russian Academy of Sciences
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Portions of this text are reprinted and reproduced in electronic form
 * from IEEE Std 1003.1, 2004 Edition, Standard for Information Technology
 * -- Portable Operating System Interface (POSIX), The Open Group Base
 * Specifications Issue 6, Copyright (C) 2001-2004 by the Institute of
 * Electrical and Electronics Engineers, Inc and The Open Group. In the
 * event of any discrepancy between this version and the original IEEE and
 * The Open Group Standard, the original IEEE and The Open Group Standard
 * is the referee document. The original Standard can be obtained online at
 * http://www.opengroup.org/unix/online.html.
 */



#define DUMPREQ

#include "fs/fs/fs_config.h"
#include "fs/fs/fs_model.seh"
#include "fs/dir/dir_model.seh"
#include "system/system/system_model.seh"
#include "process/process/process_model.seh"

#pragma SEC subsystem fs "fs.fs"



/*
   The group of functions 'fs.fs' consists of:
       __xmknod [1]
       link [2]
       remove [2]
       rename [2]
       unlink [1]
 */

/********************************************************************/
/**                      Interface Functions                       **/
/********************************************************************/

/*
Linux Standard Base Core Specification 3.1
Copyright (c) 2004, 2005 Free Standards Group

    refers

The Open Group Base Specifications Issue 6
IEEE Std 1003.1, 2004 Edition
Copyright (c) 2001-2004 The IEEE and The Open Group, All Rights reserved.

NAME

mknod - make a directory, a special file, or a regular file

SYNOPSIS

[XSI] #include <sys/stat.h>

int mknod(const char *path, mode_t mode, dev_t dev);

DESCRIPTION

The mknod() function shall create a new file named by the pathname to which the
argument path points.

The file type for path is OR'ed into the mode argument, and the application
shall select one of the following symbolic constants:

Name

Description

S_IFIFO

FIFO-special

S_IFCHR

Character-special (non-portable)

S_IFDIR

Directory (non-portable)

S_IFBLK

Block-special (non-portable)

S_IFREG

Regular (non-portable)

The only portable use of mknod() is to create a FIFO-special file. If mode is
not S_IFIFO or dev is not 0, the behavior of mknod() is unspecified.

The permissions for the new file are OR'ed into the mode argument, and may be
selected from any combination of the following symbolic constants:

Name

Description

S_ISUID

Set user ID on execution.

S_ISGID

Set group ID on execution.

S_IRWXU

Read, write, or execute (search) by owner.

S_IRUSR

Read by owner.

S_IWUSR

Write by owner.

S_IXUSR

Execute (search) by owner.

S_IRWXG

Read, write, or execute (search) by group.

S_IRGRP

Read by group.

S_IWGRP

Write by group.

S_IXGRP

Execute (search) by group.

S_IRWXO

Read, write, or execute (search) by others.

S_IROTH

Read by others.

S_IWOTH

Write by others.

S_IXOTH

Execute (search) by others.

S_ISVTX

On directories, restricted deletion flag.

The user ID of the file shall be initialized to the effective user ID of the
process. The group ID of the file shall be initialized to either the
effective group ID of the process or the group ID of the parent directory.
Implementations shall provide a way to initialize the file's group ID to the
group ID of the parent directory. Implementations may, but need not, provide
an implementation-defined way to initialize the file's group ID to the
effective group ID of the calling process. The owner, group, and other
permission bits of mode shall be modified by the file mode creation mask of the
process. The mknod() function shall clear each bit whose corresponding bit in
the file mode creation mask of the process is set.

If path names a symbolic link, mknod() shall fail and set errno to [EEXIST].

Upon successful completion, mknod() shall mark for update the st_atime,
st_ctime, and st_mtime fields of the file. Also, the st_ctime and st_mtime
fields of the directory that contains the new entry shall be marked for update.


Only a process with appropriate privileges may invoke mknod() for file types
other than FIFO-special.

RETURN VALUE

Upon successful completion, mknod() shall return 0. Otherwise, it shall return
-1, the new file shall not be created, and errno shall be set to indicate
the error.

ERRORS

The mknod() function shall fail if:

[EACCES]

A component of the path prefix denies search permission, or write permission is
denied on the parent directory.

[EEXIST]

The named file exists.

[EINVAL]

An invalid argument exists.

[EIO]

An I/O error occurred while accessing the file system.

[ELOOP]

A loop exists in symbolic links encountered during resolution of the path
argument.

[ENAMETOOLONG]

The length of a pathname exceeds {PATH_MAX} or a pathname component is longer
than {NAME_MAX}.

[ENOENT]

A component of the path prefix specified by path does not name an existing
directory or path is an empty string.

[ENOSPC]

The directory that would contain the new file cannot be extended or the file
system is out of file allocation resources.

[ENOTDIR]

A component of the path prefix is not a directory.

[EPERM]

The invoking process does not have appropriate privileges and the file type is
not FIFO-special.

[EROFS]

The directory in which the file is to be created is located on a read-only file
system.

The mknod() function may fail if:

[ELOOP]

More than {SYMLOOP_MAX} symbolic links were encountered during resolution of
the path argument.

[ENAMETOOLONG]

Pathname resolution of a symbolic link produced an intermediate result whose
length exceeds {PATH_MAX}.

*/
specification
IntT __xmknod_spec( CallContext context, IntT ver, CString *path, FileKind kind, FilePermissions *perms,  DevT dev, ErrorCode *errno )
{
  FileSystem *pre_fs = clone(getFileSystem(context));
  Bool3 isELOOP;
  CString *abspath = resolvePath_Ext(context, pre_fs, path, &isELOOP);
  File *parent_directory = getFile_FileSystem(pre_fs, getParentDir_Path(abspath));
  pre
  {
         /*
         * The value of ver shall be 1 or the behavior of __xmknod() is undefined
         */
        REQ("__xmknod.01",
            "The value of ver shall be 1 or the behavior of __xmknod() is undefined",
            1 == ver);

       /*
         * The file type for path is OR'ed into the mode argument, and the application
         * shall select one of the following symbolic constants
         */
        REQ("app.__xmknod.02", "", TODO_REQ());

        /*
         * The only portable use of mknod() is to create a FIFO-special file. If mode is
         * not S_IFIFO or dev is not 0, the behavior of mknod() is unspecified
         */
        REQ("app.__xmknod.03", "", TODO_REQ());

        /*
         * The permissions for the new file are OR'ed into the mode argument, and may be
         * selected from any combination of the following symbolic constants
         */
        REQ("app.__xmknod.04", "", TODO_REQ());

    return true;
  }
  post
  {
      FileSystem *file_system = getFileSystem(context);
      File *new_file = getFile_FileSystem(file_system, abspath);
      ProcessId processId = getProcessId_CallContext(context);

      DUMP("__xmknod_spec($(obj),$(obj),$(obj)) returns $(obj), errno=$(obj)\n",
          path, create_Integer(kind), perms, create_Integer(__xmknod_spec), create_ErrorCode(*errno));
       ERROR_BEGIN(POSIX_XMKNOD, "__xmknod.13", -1 == __xmknod_spec,  *errno )

            /*
             * The mknod() function shall fail if:
             *
             * [EACCES]
             * A component of the path prefix denies search permission, or write permission is
             * denied on the parent directory.
             */
            ERROR_SHALL3(POSIX_XMKNOD, EACCES, "__xmknod.90.01", isEACCES_dir_mkrm(context, pre_fs, abspath) )

            /*
             * The mknod() function shall fail if:
             *
             * [EEXIST]
             * The named file exists.
             */
            ERROR_SHALL(POSIX_XMKNOD, EEXIST, "__xmknod.90.02", getFile_FileSystem(pre_fs, abspath)!=NULL )

            /*
             * The mknod() function shall fail if:
             *
             * [EINVAL]
             * An invalid argument exists.
             */
            ERROR_SHALL3(POSIX_XMKNOD, EINVAL, "__xmknod.90.03", isEINVAL_dir(context, abspath) )

            /*
             * The mknod() function shall fail if:
             *
             * [EIO]
             * An I/O error occurred while accessing the file system.
             */
            ERROR_SHALL(POSIX_XMKNOD, EIO, "__xmknod.90.04", TODO_ERR(EIO) )

            /*
             * The mknod() function shall fail if:
             *
             * [ELOOP]
             * A loop exists in symbolic links encountered during resolution of the path
             * argument.
             */
            ERROR_SHALL3(POSIX_XMKNOD, ELOOP, "__xmknod.90.05", isELOOP )

            /*
             * The mknod() function shall fail if:
             *
             * [ENAMETOOLONG]
             * The length of a pathname exceeds {PATH_MAX} or a pathname component is longer
             * than {NAME_MAX}.
             */
            ERROR_SHALL3(POSIX_XMKNOD, ENAMETOOLONG, "__xmknod.90.06", isENAMETOOLONG(context, path) )

            /*
             * The mknod() function shall fail if:
             *
             * [ENOENT]
             * A component of the path prefix specified by path does not name an existing
             * directory or path is an empty string.
             */
            ERROR_SHALL3(POSIX_XMKNOD, ENOENT, "__xmknod.90.07", isENOENT_dir(context, pre_fs, getParentDir_Path(path)) )

            /*
             * The mknod() function shall fail if:
             *
             * [ENOSPC]
             * The directory that would contain the new file cannot be extended or the file
             * system is out of file allocation resources.
             */
            ERROR_SHALL(POSIX_XMKNOD, ENOSPC, "__xmknod.90.08", TODO_ERR(ENOSPC) )

            /*
             * The mknod() function shall fail if:
             *
             * [ENOTDIR]
             * A component of the path prefix is not a directory.
             */
            ERROR_SHALL3(POSIX_XMKNOD, ENOTDIR, "__xmknod.90.09", isENOTDIR_dir(context ,pre_fs, getParentDir_Path(abspath)) )

            /*
             * The mknod() function shall fail if:
             *
             * [EPERM]
             * The invoking process does not have appropriate privileges and the file type is
             * not FIFO-special.
             */
            ERROR_SHALL(POSIX_XMKNOD, EPERM, "__xmknod.90.10", TODO_ERR(EPERM) )

            /*
             * The mknod() function shall fail if:
             *
             * [EROFS]
             * The directory in which the file is to be created is located on a read-only file
             * system.
             */
            ERROR_SHALL(POSIX_XMKNOD, EROFS, "__xmknod.90.11", TODO_ERR(EROFS) )

            /*
             * The mknod() function may fail if:
             *
             * [ELOOP]
             * More than {SYMLOOP_MAX} symbolic links were encountered during resolution of
             * the path argument.
             */
            ERROR_MAY3(POSIX_XMKNOD, ELOOP, "__xmknod.91.01", isELOOP)

            /*
             * The mknod() function may fail if:
             *
             * [ENAMETOOLONG]
             * Pathname resolution of a symbolic link produced an intermediate result whose
             * length exceeds {PATH_MAX}.
             */
            ERROR_MAY3(POSIX_XMKNOD, ENAMETOOLONG, "__xmknod.91.02", isENAMETOOLONG(context, path))

        ERROR_END()

        /*
         * The mknod() function shall create a new file named by the pathname to which the
         * argument path points
         */
        REQ("__xmknod.01",
            "The mknod() function shall create a new file",
            NULL!=getFile_FileSystem(file_system, abspath));

        /*
         * The user ID of the file shall be initialized to the effective user ID of the
         * process
         */
        REQ("__xmknod.05",
            "The user ID of the file shall be initialized to the effective "
            "user ID of the process",
            equals(getUserId_File(new_file),
                   getEffectiveUserId_ProcessId(processId)));

        /*
         * The group ID of the file shall be initialized to either the effective group ID
         * of the process or the group ID of the parent directory. Implementations shall
         * provide a way to initialize the file's group ID to the group ID of the parent
         * directory. Implementations may, but need not, provide an implementation-
         * defined way to initialize the file's group ID to the effective group ID of the
         * calling process.
         */
        REQ("__xmknod.06",
            "The group ID of the file shall be initialized to either the "
            "effective group ID of the process or the group ID of the parent "
            "directory",
            equals(getGroupId_File(new_file),
                   getEffectiveGroupId_ProcessId(processId))   // process effective ID

                || equals(getGroupId_File(new_file),
                          getGroupId_File(parent_directory)));

        /*
         * The owner, group, and other permission bits of mode shall be modified by the
         * file mode creation mask of the process. The mknod() function shall clear each
         * bit whose corresponding bit in the file mode creation mask of the process is
         * set.
         */
        REQ("__xmknod.07",
            "The owner, group, and other permission bits of mode shall be "
            "modified by the file mode creation mask of the process",
            equals(getPermissions_File(new_file),
                   getProcessModifiedFilePermissions(processId, perms)));

        /*
         * If path names a symbolic link, mknod() shall fail and set errno to [EEXIST]
         */
        REQ("__xmknod.08", "", TODO_REQ());

        /*
         * Upon successful completion, mknod() shall mark for update the st_atime,
         * st_ctime, and st_mtime fields of the file
         */
        REQ("__xmknod.09", "", TODO_REQ());

        /*
         * Also, the st_ctime and st_mtime fields of the directory that contains the new
         * entry shall be marked for update
         */
        REQ("__xmknod.10", "", TODO_REQ());

        /*
         * Only a process with appropriate privileges may invoke mknod() for file types
         * other than FIFO-special
         */
        REQ("__xmknod.11", "", TODO_REQ());

        /*
         * Upon successful completion, mknod() shall return 0
         */
        REQ("__xmknod.12",
            "Upon successful completion, mknod() shall return 0",
            0==__xmknod_spec);




    return true;
  }
}

File *mknod_FileSystem(FileSystem *file_system, ProcessId processId, CString *path, FileKind kind, FilePermissions *perms, DevT dev)
{
    File *new_node;
    File *parent_directory;

    if((new_node = getFile_FileSystem( file_system, path ))!=NULL)
    {
        traceFormattedUserInfo("mknod_FileSystem: directory $(obj) shouldn't exist", path);
        return NULL;
    }

    // add directory to model
    new_node = registerFile_Kind( file_system, path, kind );

    // set permissions
    setPermissions_File(new_node, getProcessModifiedFilePermissions(processId, perms));

    // set effective uid
    setUserId_File(new_node, getEffectiveUserId_ProcessId(processId));

    // set effective gid
    setGroupId_File(new_node, getEffectiveGroupId_ProcessId(processId));

    // increment hardlinks count
    incLinksCount_File( file_system, new_node, (NLinkT)1 );

    // mark times for update
    new_node->atime_updated = false;
    new_node->mtime_updated = false;
    new_node->ctime_updated = false;

    parent_directory = getTheOnlyParentDirectory_File(file_system, new_node);

    // mark parent dir ctime and mtime for update
    if(parent_directory)
    {
        parent_directory->atime_updated = false;
        parent_directory->ctime_updated = false;
        parent_directory->mtime_updated = false;
    }

    return new_node;
}

void on__xmknod( CallContext context, IntT ver, CString *path, FileKind kind, FilePermissions *perms,  DevT dev, IntT retval, ErrorCode *errno )
{
    FileSystem *fs = getFileSystem(context);
    ProcessId processId = getProcessId_CallContext(context);

    if(retval==0)
    {
        if(!mknod_FileSystem(fs, processId, path, kind, perms, dev))
            setBadVerdict("_xmknode() failed to create existing node");
    }
}

/*
Linux Standard Base Core Specification 3.1
Copyright (c) 2004, 2005 Free Standards Group

NAME

    link -- create a link to a file

SYNOPSIS

    #include <unistd.h>

    int link(const char * path1, const char * path2);

DESCRIPTION

    The link() function shall behave as specified in ISO POSIX (2003), except
    with differences as listed below.

    Need Not Follow Symlinks

    ISO POSIX (2003) specifies that pathname resolution shall follow symbolic
    links during pathname resolution unless the function is required to act on
    the symbolic link itself, or certain arguments direct that the function act
    on the symbolic link itself. The link() function in ISO POSIX (2003)
    contains no such requirement to operate on a symbolic link. However, a
    conforming LSB implementation need not follow a symbolic link for the path1
    argument.
*/
/*
Linux Standard Base Core Specification 3.1
Copyright (c) 2004, 2005 Free Standards Group

      refers

The Open Group Base Specifications Issue 6
IEEE Std 1003.1, 2004 Edition
Copyright (c) 2001-2004 The IEEE and The Open Group, All Rights reserved.

NAME

    link - link to a file

SYNOPSIS

    #include <unistd.h>

    int link(const char *path1, const char *path2);

DESCRIPTION

    The link() function shall create a new link (directory entry) for the
    existing file, path1.

    The path1 argument points to a pathname naming an existing file. The path2
    argument points to a pathname naming the new directory entry to be created
    . The link() function shall atomically create a new link for the existing
    file and the link count of the file shall be incremented by one.

    If path1 names a directory, link() shall fail unless the process has
    appropriate privileges and the implementation supports using link() on
    directories.

    Upon successful completion, link() shall mark for update the st_ctime
    field of the file. Also, the st_ctime and st_mtime fields of the directory
    that contains the new entry shall be marked for update.

    If link() fails, no link shall be created and the link count of the file
    shall remain unchanged.

    The implementation may require that the calling process has permission to
    access the existing file.

RETURN VALUE

    Upon successful completion, 0 shall be returned. Otherwise, -1 shall be
    returned and errno set to indicate the error.

ERRORS

    The link() function shall fail if:

    [EACCES]
        A component of either path prefix denies search permission, or the
        requested link requires writing in a directory that denies write
        permission, or the calling process does not have permission to access
        the existing file and this is required by the implementation.
    [EEXIST]
        The path2 argument resolves to an existing file or refers to a
        symbolic link.
    [ELOOP]
        A loop exists in symbolic links encountered during resolution of the
        path1 or path2 argument.
    [EMLINK]
        The number of links to the file named by path1 would exceed {LINK_MAX}.
    [ENAMETOOLONG]
        The length of the path1 or path2 argument exceeds {PATH_MAX} or a
        pathname component is longer than {NAME_MAX}.
    [ENOENT]
        A component of either path prefix does not exist; the file named by
        path1 does not exist; or path1 or path2 points to an empty string.
    [ENOSPC]
        The directory to contain the link cannot be extended.
    [ENOTDIR]
        A component of either path prefix is not a directory.
    [EPERM]
        The file named by path1 is a directory and either the calling process
        does not have appropriate privileges or the implementation prohibits
        using link() on directories.
    [EROFS]
        The requested link requires writing in a directory on a read-only file
        system.
    [EXDEV]
        The link named by path2 and the file named by path1 are on different
        file systems and the implementation does not support links between
        file systems.
    [EXDEV]
        [XSR] [Option Start] path1 refers to a named STREAM. [Option End]

    The link() function may fail if:

    [ELOOP]
        More than {SYMLOOP_MAX} symbolic links were encountered during
        resolution of the path1 or path2 argument.
    [ENAMETOOLONG]
        As a result of encountering a symbolic link in resolution of the path1
        or path2 argument, the length of the substituted pathname string
        exceeded {PATH_MAX}.

*/
specification
IntT link_spec( CallContext context, CString *path1, CString *path2, ErrorCode *errno, CancelStatus status )
{
  FileSystem *pre_fs = clone(getFileSystem(context));
  Bool3 isELOOP, isELOOP2;
  CString *abspath1 = resolvePath_Ext(context, pre_fs, path1, &isELOOP);
  CString *abspath2 = resolvePath_Ext(context, pre_fs, path2, &isELOOP2);
  pre
  {
    return true;
  }
  post
  {
      FileSystem *file_system = getFileSystem(context);
      File *new_link_entry = getFile_FileSystem(file_system, abspath2);
      File *old_link_entry = getFile_FileSystem(pre_fs, abspath1);
      NLinkTObj *nlinks = NULL;
      File *parent_directory = getFile_FileSystem(pre_fs, getParentDir_Path(abspath2));

      DUMP("link_spec($(obj),$(obj)) returns $(obj), errno=$(obj)\n", path1, path2,
          create_Integer(link_spec), create_ErrorCode(*errno));

      ERROR_BEGIN(POSIX_LINK, "link.09", -1 == link_spec,  *errno )

            /*
             * The link() function shall fail if:
             *
             * [EACCES]
             * A component of either path prefix denies search permission, or the requested
             * link requires writing in a directory that denies write permission, or the
             * calling process does not have permission to access the existing file and this
             * is required by the implementation.
             */
            ERROR_SHALL3(POSIX_LINK, EACCES, "link.90.01", isEACCES_dir_mkrm(context, pre_fs, abspath1) )

            /*
             * The link() function shall fail if:
             *
             * [EEXIST]
             * The path2 argument resolves to an existing file or refers to a symbolic link.
             */
            ERROR_SHALL3(POSIX_LINK, EEXIST, "link.90.02", or_Bool3(
                doesFileExist_FileSystem(pre_fs, abspath2),
                isFileKindOf_FileSystem(pre_fs, abspath2, SymbolicLinkFile)))

            /*
             * The link() function shall fail if:
             *
             * [ELOOP]
             * A loop exists in symbolic links encountered during resolution of the path1 or
             * path2 argument.
             */
            ERROR_SHALL3(POSIX_LINK, ELOOP, "link.90.03", or_Bool3(isELOOP,isELOOP2))

            /*
             * The link() function shall fail if:
             *
             * [EMLINK]
             * The number of links to the file named by path1 would exceed {LINK_MAX}.
             */
            ERROR_SHALL3(POSIX_LINK, EMLINK, "link.90.04", or_Bool3(isEMLINK(context, pre_fs, abspath1),
                        isEMLINK(context, pre_fs, abspath2)))

            /*
             * The link() function shall fail if:
             *
             * [ENAMETOOLONG]
             * The length of the path1 or path2 argument exceeds {PATH_MAX} or a pathname
             * component is longer than {NAME_MAX}.
             */
            ERROR_SHALL3(POSIX_LINK, ENAMETOOLONG, "link.90.05", or_Bool3(isENAMETOOLONG(context, abspath1),
                isENAMETOOLONG(context, abspath1)))

            /*
             * The link() function shall fail if:
             *
             * [ENOENT]
             * A component of either path prefix does not exist; the file named by path1 does
             * not exist; or path1 or path2 points to an empty string.
             */
             // TODO: maybe ENOENT does wrong thing (path2 MAY not exist
            ERROR_SHALL3(POSIX_LINK, ENOENT, "link.90.06", or_Bool3(
                isENOENT_dir(context, pre_fs, abspath1),
                isENOENT_dir(context, pre_fs, abspath2)))


            /*
             * The link() function shall fail if:
             *
             * [ENOSPC]
             * The directory to contain the link cannot be extended.
             */
            ERROR_UNCHECKABLE(POSIX_LINK, ENOSPC, "link.90.07", "Disk space is not modeled" )

            /*
             * The link() function shall fail if:
             *
             * [ENOTDIR]
             * A component of either path prefix is not a directory.
             */
            ERROR_SHALL3(POSIX_LINK, ENOTDIR, "link.90.08", or_Bool3(
                isENOTDIR_dir(context, pre_fs, getParentDir_Path(abspath1)),
                isENOTDIR_dir(context, pre_fs, getParentDir_Path(abspath2))))

            /*
             * The link() function shall fail if:
             *
             * [EPERM]
             * The file named by path1 is a directory and either the calling process does not
             * have appropriate privileges or the implementation prohibits using link() on
             * directories.
             */
             // TODO: rmdir's isEPERM doesn't match this case?
            ERROR_SHALL3(POSIX_LINK, EPERM, "link.90.09", isEPERM_rmdir(context, pre_fs, abspath1) )

            /*
             * The link() function shall fail if:
             *
             * [EROFS]
             * The requested link requires writing in a directory on a read-only file system.
             */
            ERROR_UNCHECKABLE(POSIX_LINK, EROFS, "link.90.10", "Readonly file system status not modeled" )

            /*
             * The link() function shall fail if:
             *
             * [EXDEV]
             * The link named by path2 and the file named by path1 are on different file
             * systems and the implementation does not support links between file systems.
             */
            /*
             * The link() function shall fail if:
             *
             * [EXDEV]
             * [XSR] path1 refers to a named STREAM.
             */
            ERROR_UNCHECKABLE(POSIX_LINK, EXDEV, "link.90.11;link.90.12",
                "Different file systems not modeled"
                        )

            /*
             * The link() function may fail if:
             *
             * [ELOOP]
             * More than {SYMLOOP_MAX} symbolic links were encountered during resolution of
             * the path1 or path2 argument.
             */
            ERROR_MAY3(POSIX_LINK, ELOOP, "link.92.01", isELOOP)

            /*
             * The link() function may fail if:
             *
             * [ENAMETOOLONG]
             * As a result of encountering a symbolic link in resolution of the path1 or path2
             * argument, the length of the substituted pathname string exceeded {PATH_MAX}.
             */
            ERROR_MAY3(POSIX_LINK, ENAMETOOLONG, "link.92.02", or_Bool3(isENAMETOOLONG(context, abspath1),
                isENAMETOOLONG(context,abspath2)))

    ERROR_END()
          /*
         * The link() function shall create a new link (directory entry) for the existing
         * file, path1.
         */
        REQ("link.01", "link() didn't create appropriate directory entry", NULL!=new_link_entry);

        /*
         * The link() function shall atomically create a new link for the existing file
         * and the link count of the file shall be incremented by one
         */
        REQ("link.02", "link() should increment link count by 1", (nlinks=diffLinksCount_File(old_link_entry, new_link_entry))==NULL || *nlinks==1);

        /*
         * If path1 names a directory, link() shall fail unless the process has
         * appropriate privileges and the implementation supports using link() on
         * directories
         */
        REQ("link.03", "If path1 names a directory, link() shall fail",
            isFileKindOf_FileSystem(pre_fs, abspath1, DirectoryFile)!=True_Bool3
            || NULL==old_link_entry
            || False_Bool3!=hasAppropriatePrivileges(context, link_AccessPrivileges, old_link_entry->fileid)
            );

        /*
         * Upon successful completion, link() shall mark for update the st_ctime field of
         * the file
         */
        REQ("link.04", "link() shall mark for update st_ctime", !new_link_entry->ctime_updated );

        /*
         * Also, the st_ctime and st_mtime fields of the directory that contains the new
         * entry shall be marked for update.
         */
        REQ("link.05", "link() shall mark for update st_ctime and st_mtime of parent dir", !parent_directory->ctime_updated &&
                !parent_directory->mtime_updated);

        /*
         * If link() fails, no link shall be created and the link count of the file shall
         * remain unchanged
         */
        REQ("link.06", "link counts shall remain if link() fails", 0==link_spec || (nlinks=diffLinksCount_File(old_link_entry, new_link_entry))==NULL || 0==*nlinks);

        /*
         * The implementation may require that the calling process has permission to
         * access the existing file.
         */
        REQ("link.07", "", TODO_REQ());

        /*
         * Upon successful completion, 0 shall be returned.
         */
        REQ("link.08", "0 shall be returned by link() on success", 0==link_spec);

        /*
         * ISO POSIX (2003) specifies that pathname resolution shall follow symbolic links
         * during pathname resolution unless the function is required to act on the
         * symbolic link itself, or certain arguments direct that the function act on the
         * symbolic link itself. The link() function in ISO POSIX (2003) contains no such
         * requirement to operate on a symbolic link. However, a conforming LSB
         * implementation need not follow a symbolic link for the path1 argument.
         */
        REQ("link.30", "", TODO_REQ());

    return true;
  }
}

void on_link( CallContext context, CString *path1, CString *path2, IntT retval, ErrorCode *errno )
{
    FileSystem* file_system = getFileSystem(context);
    Bool3 isELOOP;
    CString *abspath1 = resolvePath_Ext(context, file_system, path1, &isELOOP);
    CString *abspath2 = resolvePath_Ext(context, file_system, path2, &isELOOP);

    if (retval == 0)
    {
        link_FileSystem(file_system, abspath1, abspath2);
    }
}

void linkFile_FileSystem(FileSystem *fs, CString *path2, File *file1)
{
    File *dir = fileExist_FileSystem(fs, getParentDir_Path(path2), DirectoryFile);
    CString *basename = getBaseName_Path(path2);
    registerDirLink_Directory(file1, dir, basename);
    registerUpwardLink_File(file1,dir, basename);
    incLinksCount_File(fs, file1, (NLinkT) 1);
}

void link_FileSystem(FileSystem *fs, CString *path1, CString *path2)
{
    File *file1 = fileExist_FileSystem(fs, path1, UnknownFileKind);
    linkFile_FileSystem(fs,path2,file1);
}



/*
Linux Standard Base Core Specification 3.1
Copyright (c) 2004, 2005 Free Standards Group

    refers

The Open Group Base Specifications Issue 6
IEEE Std 1003.1, 2004 Edition
Copyright (c) 2001-2004 The IEEE and The Open Group, All Rights reserved.

NAME

remove - remove a file

SYNOPSIS

#include <stdio.h>

int remove(const char *path);

DESCRIPTION

[CX] The functionality described on this reference page is aligned with the
ISO C standard. Any conflict between the requirements described here and
the ISO C standard is unintentional. This volume of IEEE Std
1003.1-2001 defers to the ISO C standard.

The remove() function shall cause the file named by the pathname pointed to by
path to be no longer accessible by that name. A subsequent attempt to
open that file using that name shall fail, unless it is created anew.

[CX] If path does not name a directory, remove(path) shall be equivalent to
unlink(path).

If path names a directory, remove(path) shall be equivalent to rmdir(path).

RETURN VALUE

[CX] Refer to rmdir() or unlink().

ERRORS

[CX] Refer to rmdir() or unlink().
*/

specification
IntT remove_spec( CallContext context, CString *path, ErrorCode *errno, CancelStatus status )
{
  FileSystem *pre_fs = clone(getFileSystem(context));
  Bool3 isELOOP;
  CString *abspath = resolvePathSymLink_Ext(context, pre_fs, path, &isELOOP);

    pre
  {
    return true;
  }
  post
  {
      File *old_link = getFile_FileSystem(pre_fs, abspath);
      File *old_dest = getSymLink_File(context, pre_fs, old_link, &isELOOP);
      FileSystem *file_system = getFileSystem(context);
      File *new_link= getFile_FileSystem(file_system, abspath);
      File *new_dest = getSymLink_File(context, file_system, new_link, &isELOOP);
      Bool3 isSymLink = isFileKindOf_FileSystem(pre_fs, abspath, SymbolicLinkFile);
      NLinkTObj *nlinks=NULL;
      File *parent_directory = getFile_FileSystem(pre_fs, getParentDir_Path(abspath));
      Bool3 is_dir = isFileKindOf_FileSystem(pre_fs,abspath,DirectoryFile);

      DUMP("remove_spec($(obj)) returns $(obj), errno=$(obj)\n", path,
          create_Integer(remove_spec), create_ErrorCode(*errno));

      if(True_Bool3!=is_dir)
      {
        // case of file: remove===unlink
       ERROR_BEGIN(POSIX_REMOVE, "remove.90.01", -1==remove_spec,  *errno )

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [EACCES]
             * Search permission is denied for a component of the path prefix, or write
             * permission is denied on the directory containing the directory entry to be
             * removed.
             */
            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [EPERM] or [EACCES]
             * [XSI] The S_ISVTX flag is set on the directory containing the file referred
             * to by the path argument and the caller is not the file owner, nor is the
             * caller the directory owner, nor does the caller have appropriate privileges.
             */
            ERROR_SHALL3(POSIX_REMOVE, EACCES, "remove.90.01;remove.90.08",
                    isEACCES_dir_mkrm(context, pre_fs, path))

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [EBUSY]
             * The file named by the path argument cannot be unlinked because it is being used
             * by the system or another process and the implementation considers this an
             * error.
             */
            ERROR_SHALL(POSIX_REMOVE, EBUSY, "remove.90.02", TODO_ERR(EBUSY) )

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [ELOOP]
             * A loop exists in symbolic links encountered during resolution of the path
             * argument.
             */
            ERROR_SHALL3(POSIX_REMOVE, ELOOP, "remove.90.03", isELOOP )

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [ENAMETOOLONG]
             * The length of the path argument exceeds {PATH_MAX} or a pathname component is
             * longer than {NAME_MAX}.
             */
            ERROR_SHALL3(POSIX_REMOVE, ENAMETOOLONG, "remove.90.04", isENAMETOOLONG(context, path) )

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [ENOENT]
             * A component of path does not name an existing file or path is an empty string.
             */
            ERROR_SHALL3(POSIX_REMOVE, ENOENT, "remove.90.05", isENOENT_dir(context, pre_fs, path) )

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [ENOTDIR]
             * A component of the path prefix is not a directory.
             */
            ERROR_SHALL3(POSIX_REMOVE, ENOTDIR, "remove.90.06", isENOTDIR_dir(context, pre_fs, getParentDir_Path(path)) )

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [EPERM]
             * The file named by path is a directory, and either the calling process does not
             * have appropriate privileges, or the implementation prohibits using unlink() on
             * directories.
             */
            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [EPERM] or [EACCES]
             * [XSI] The S_ISVTX flag is set on the directory containing the file referred
             * to by the path argument and the caller is not the file owner, nor is the
             * caller the directory owner, nor does the caller have appropriate privileges.
             */
            ERROR_SHALL3(POSIX_REMOVE, EPERM, "remove.90.07;remove.90.08",
                        or_Bool3(isEPERM_unlink(context, pre_fs, abspath),
                        POSIX_OPTION(context, XSI)?isEPERM_rmdir(context, pre_fs, abspath):False_Bool3)
                        )

            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [EROFS]
             * The directory entry to be unlinked is part of a read-only file system.
             */
            ERROR_SHALL(POSIX_REMOVE, EROFS, "remove.90.09", TODO_ERR(EROFS) )

            /*
             * The unlink() function may fail and not unlink the file if:
             *
             * [EBUSY]
             * [XSI] The file named by path is a named STREAM.
             */
            ERROR_MAY(POSIX_REMOVE, EBUSY, "remove.92.01", TODO_ERR(EBUSY))

            /*
             * The unlink() function may fail and not unlink the file if:
             *
             * [ELOOP]
             * More than {SYMLOOP_MAX} symbolic links were encountered during resolution of
             * the path argument.
             */
            ERROR_MAY(POSIX_REMOVE, ELOOP, "remove.92.02", TODO_ERR(ELOOP))

            /*
             * The unlink() function may fail and not unlink the file if:
             *
             * [ENAMETOOLONG]
             * As a result of encountering a symbolic link in resolution of the path argument,
             * the length of the substituted pathname string exceeded {PATH_MAX}.
             */
            ERROR_MAY3(POSIX_REMOVE, ENAMETOOLONG, "remove.92.03", isENAMETOOLONG(context, abspath))

            /*
             * The unlink() function may fail and not unlink the file if:
             *
             * [ETXTBSY]
             * The entry to be unlinked is the last directory entry to a pure procedure
             * (shared text) file that is being executed.
             */
            ERROR_MAY(POSIX_REMOVE, ETXTBSY, "remove.92.04", TODO_ERR(ETXTBSY))

        ERROR_END()

        /*
         * The unlink() function shall remove a link to a file
         */
        REQ("remove.05", "The unlink() function shall remove a link to a file",
            new_link==NULL);

        /*
         * If path names a symbolic link, unlink() shall remove the symbolic link named
         * by path and shall not affect any file or directory named by the contents of the
         * symbolic link
         */
        REQ("remove.06",
            "If path names a symbolic link, unlink() shall remove the "
            "symbolic link named by path",
            True_Bool3!=isSymLink
            || new_dest==old_dest);

        /*
         * Otherwise, unlink() shall remove the link named by the pathname pointed to by
         * path and shall decrement the link count of the file referenced by the link
         */
        REQ("remove.07", "Otherwise, unlink() shall remove the link named by "
            "the pathname pointed to by path",
            False_Bool3!=isSymLink
            || NULL==(nlinks=diffLinksCount_File(old_dest, new_dest))
            || *nlinks==-1);

        /*
         * When the file's link count becomes 0 and no process has the file open, the
         * space occupied by the file shall be freed and the file shall no longer be
         * accessible
         */
        REQ("remove.08",
            "When the file's link count becomes 0 and no process has the "
            "file open",
            nlinks==NULL || *nlinks!=0 || new_dest==NULL);

        /*
         * If one or more processes have the file open when the last link is removed, the
         * link shall be removed before unlink() returns, but the removal of the file
         * contents shall be postponed until all references to the file are closed.
         */
        REQ("remove.09", "", TODO_REQ());

        /*
         * The path argument shall not name a directory unless the process has appropriate
         * privileges and the implementation supports using unlink() on directories
         */
        // TODO: appropriate privileges
        REQ("remove.10",
            "The path argument shall not name a directory unless the process "
            "has appropriate privileges",
            True_Bool3!=isFileKindOf_FileSystem(pre_fs, abspath, DirectoryFile));

        /*
         * Upon successful completion, unlink() shall mark for update the st_ctime and
         * st_mtime fields of the parent directory
         */
        REQ("remove.11",
            "unlink() shall mark for update the st_ctime and st_mtime fields "
            "of the parent directory",
            !parent_directory->ctime_updated &&
            !parent_directory->mtime_updated);

        /*
         * Also, if the file's link count is not 0, the st_ctime field of the file shall
         * be marked for update
         */
        REQ("remove.12",
            "if the file's link count is not 0",
            nlinks==NULL || *nlinks!=0 || new_dest==NULL || !new_dest->ctime_updated);

        /*
         * Upon successful completion, 0 shall be returned
         */
        REQ("remove.13",
            "Upon successful completion, 0 shall be returned",
            0==remove_spec);


        /*
         * If -1 is returned, the named file shall not be changed
         */
        REQ("remove.15", "", TODO_REQ());
      }

      if(True_Bool3==is_dir)
      {
          // case of directory: remove===rmdir

         ERROR_BEGIN(POSIX_REMOVE, "remove.38", -1==remove_spec,  *errno )

            /*
             * The rmdir() function shall fail if
             *
             * [EACCES]
             * Search permission is denied on a component of the path prefix, or write
             * permission is denied on the parent directory of the directory to be removed.
             */


            /*
             * The rmdir() function shall fail if
             *
             * [EBUSY]
             * The directory to be removed is currently in use by the system or some process
             * and the implementation considers this to be an error.
             */
            ERROR_SHALL3(POSIX_REMOVE, EBUSY, "remove.80.02", TODO_ERR(EBUSY) )

            /*
             * The rmdir() function shall fail if
             *
             * [EEXIST] or [ENOTEMPTY]
             * The path argument names a directory that is not an empty directory, or there
             * are hard links to the directory other than dot or a single entry in dot-dot.
             */
            ERROR_SHALL3(POSIX_REMOVE, EEXIST, "remove.80.03", isENOTEMPTY_dir(context, pre_fs, abspath) )

            ERROR_SHALL3(POSIX_REMOVE, ENOTEMPTY, "remove.80.03", isENOTEMPTY_dir(context, pre_fs, abspath) )

            /*
             * The rmdir() function shall fail if
             *
             * [EINVAL]
             * The path argument contains a last component that is dot.
             */
            ERROR_SHALL3(POSIX_REMOVE, EINVAL, "remove.80.04", isEINVAL_dir(context, path) )

            /*
             * The rmdir() function shall fail if
             *
             * [EIO]
             * A physical I/O error has occurred.
             */
            ERROR_SHALL3(POSIX_REMOVE, EIO, "remove.80.05", TODO_ERR(EIO) )

            /*
             * The rmdir() function shall fail if
             *
             * [ELOOP]
             * A loop exists in symbolic links encountered during resolution of the path
             * argument.
             */
            ERROR_SHALL3(POSIX_REMOVE, ELOOP, "remove.80.06", isELOOP )

            /*
             * The rmdir() function shall fail if
             *
             * [ENAMETOOLONG]
             * The length of the path argument exceeds {PATH_MAX} or a pathname component is
             * longer than {NAME_MAX}.
             */
            ERROR_SHALL3(POSIX_REMOVE, ENAMETOOLONG, "remove.80.07", isENAMETOOLONG(context, path) )

            /*
             * The rmdir() function shall fail if
             *
             * [ENOENT] A component of path does not name an existing file, or the path
             * argument names a nonexistent directory or points to an empty string.
             */
            ERROR_SHALL3(POSIX_REMOVE, ENOENT, "remove.80.08", isENOENT_dir(context, pre_fs, abspath) )

            /*
             * The rmdir() function shall fail if
             *
             * [ENOTDIR]
             * A component of path is not a directory.
             */
            ERROR_SHALL3(POSIX_REMOVE, ENOTDIR, "remove.80.10", isENOTDIR_dir(context, pre_fs, getParentDir_Path(abspath)) )

            /*
             * The rmdir() function shall fail if
             *
             * [EPERM] or [EACCES]
             * [XSI] The S_ISVTX flag is set on the parent directory of the directory to be
             * removed and the caller is not the owner of the directory to be removed, nor is
             * the caller the owner of the parent directory, nor does the caller have the
             * appropriate privileges.
             */
            /*
             * The rmdir() function shall fail if
             *
             * [EPERM] or [EACCES]
             * [XSI] The S_ISVTX flag is set on the parent directory of the directory to be
             * removed and the caller is not the owner of the directory to be removed, nor is
             * the caller the owner of the parent directory, nor does the caller have the
             * appropriate privileges.
             */

            if(POSIX_OPTION(context, XSI))
            {
                ERROR_SHALL3(POSIX_REMOVE, EPERM, "remove.80.11", isEPERM_rmdir(context, pre_fs, abspath) )
                ERROR_SHALL3(POSIX_REMOVE, EACCES, "remove.80.01;remove.80.11",    isEACCES_dir_mkrm(context, pre_fs, abspath))
            }
            /*
             * The rmdir() function shall fail if
             *
             * [EROFS]
             * The directory entry to be removed resides on a read-only file system.
             */
            ERROR_SHALL(POSIX_REMOVE, EROFS, "remove.80.12", TODO_ERR(EROFS) )

            /*
             * The rmdir() function may fail if:
             *
             * [ELOOP]
             * More than {SYMLOOP_MAX} symbolic links were encountered during resolution of
             * the path argument.
             */
            ERROR_MAY3(POSIX_REMOVE, ELOOP, "remove.81.01", isELOOP)

            /*
             * The rmdir() function may fail if:
             *
             * [ENAMETOOLONG]
             * As a result of encountering a symbolic link in resolution of the path argument,
             * the length of the substituted pathname string exceeded {PATH_MAX}.
             */
            ERROR_MAY3(POSIX_REMOVE, ENAMETOOLONG, "remove.81.02", isENAMETOOLONG(context, abspath))

        ERROR_END()

      /*
         * The rmdir() function shall remove a directory whose name is given by path. The
         * directory shall be removed only if it is an empty directory.
         */
        REQ("remove.31",
            "The rmdir() function shall remove a directory",
            new_link==NULL);

        /*
         * If the directory is the root directory or the current working directory of any
         * process, it is unspecified whether the function succeeds, or whether it shall
         * fail and set errno to [EBUSY].
         */
        REQ("remove.40", "", TODO_REQ());

        /*
         * If path names a symbolic link, then rmdir() shall fail and set errno to
         * [ENOTDIR].
         */

//        REQ("remove.32", "", TODO_REQ());    // Seems to be not the case TODO: remove from markup

        /*
         * If the path argument refers to a path whose final component is either dot or
         * dot-dot, rmdir() shall fail.
         */
//        REQ("remove.33", "", TODO_REQ());    // TODO: remove from markup (handled in errors)

        /*
         * If the directory's link count becomes 0 and no process has the directory open,
         * the space occupied by the directory shall be freed and the directory shall no
         * longer be accessible.
         */
        REQ("remove.34", "", TODO_REQ());

        /*
         * If one or more processes have the directory open when the last link is
         * removed, the dot and dot-dot entries, if present, shall be removed before
         * rmdir() returns and no new entries may be created in the directory, but the
         * directory shall not be removed until all references to the directory are closed
         */
        REQ("remove.35", "", TODO_REQ());

        /*
         * If the directory is not an empty directory, rmdir() shall fail and set errno to
         * [EEXIST] or [ENOTEMPTY].
         */
//        REQ("remove.41", "", TODO_REQ());        // handled in errors TODO :Remove from markup

        /*
         * Upon successful completion, the rmdir() function shall mark for update the
         * st_ctime and st_mtime fields of the parent directory.
         */
        REQ("remove.36",
            "the rmdir() function shall mark for update the st_ctime and "
            "st_mtime fields of the parent directory",
            !parent_directory->ctime_updated
            && !parent_directory->mtime_updated);

        /*
         * Upon successful completion, the function rmdir() shall return 0.
         */
        REQ("remove.37",
            "Upon successful completion, the function rmdir() shall return 0",
            0==remove_spec);

       }
            /*
         * The remove() function shall cause the file named by the pathname pointed to by
         * path to be no longer accessible by that name
         */
        REQ("remove.01",
            "The remove() function shall cause the file to be no longer "
            "accessible by name",
            new_link==NULL);

        /*
         * A subsequent attempt to open that file using that name shall fail, unless it is
         * created anew // TODO???
         */
        REQ("remove.02", "", TODO_REQ());

    return true;
  }
}

void on_remove(  CallContext context, CString *path, IntT retval, ErrorCode *errno )
{
    FileSystem* file_system = getFileSystem(context);
    Bool3 isELOOP;
    CString *absPath = resolvePathSymLink_Ext(context, file_system, path, &isELOOP);

    if (retval == 0)
    {
        if(True_Bool3 == isFileKindOf_FileSystem(file_system, path, DirectoryFile))
            rmdir_FileSystem(file_system, absPath);
        else
            unlink_FileSystem(file_system, absPath, false);
    }
}


/*
Linux Standard Base Core Specification 3.1
Copyright (c) 2004, 2005 Free Standards Group

    refers

The Open Group Base Specifications Issue 6
IEEE Std 1003.1, 2004 Edition
Copyright (c) 2001-2004 The IEEE and The Open Group, All Rights reserved.

NAME

NAME

    rename - rename a file

SYNOPSIS

    #include <stdio.h>

    int rename(const char *old, const char *new);

DESCRIPTION

    [CX] [Option Start] The functionality described on this reference page is
    aligned with the ISO C standard. Any conflict between the requirements
    described here and the ISO C standard is unintentional. This volume of
    IEEE Std 1003.1-2001 defers to the ISO C standard. [Option End]

    The rename() function shall change the name of a file. The old argument
    points to the pathname of the file to be renamed. The new argument points
    to the new pathname of the file.

    [CX] [Option Start] If either the old or new argument names a symbolic link
    , rename() shall operate on the symbolic link itself, and shall not
    resolve the last component of the argument. If the old argument and the
    new argument resolve to the same existing file, rename() shall return
    successfully and perform no other action.

    If the old argument points to the pathname of a file that is not a
    directory, the new argument shall not point to the pathname of a directory
    . If the link named by the new argument exists, it shall be removed and
    old renamed to new. In this case, a link named new shall remain visible to
    other processes throughout the renaming operation and refer either to the
    file referred to by new or old before the operation began. Write access
    permission is required for both the directory containing old and the
    directory containing new.

    If the old argument points to the pathname of a directory, the new
    argument shall not point to the pathname of a file that is not a directory
    . If the directory named by the new argument exists, it shall be removed
    and old renamed to new. In this case, a link named new shall exist
    throughout the renaming operation and shall refer either to the directory
    referred to by new or old before the operation began. If new names an
    existing directory, it shall be required to be an empty directory.

    If the old argument points to a pathname of a symbolic link, the symbolic
    link shall be renamed. If the new argument points to a pathname of a
    symbolic link, the symbolic link shall be removed.

    The new pathname shall not contain a path prefix that names old. Write
    access permission is required for the directory containing old and the
    directory containing new. If the old argument points to the pathname of a
    directory, write access permission may be required for the directory named
    by old, and, if it exists, the directory named by new.

    If the link named by the new argument exists and the file's link count
    becomes 0 when it is removed and no process has the file open, the space
    occupied by the file shall be freed and the file shall no longer be
    accessible. If one or more processes have the file open when the last link
    is removed, the link shall be removed before rename() returns, but the
    removal of the file contents shall be postponed until all references to
    the file are closed.

    Upon successful completion, rename() shall mark for update the st_ctime
    and st_mtime fields of the parent directory of each file.

    If the rename() function fails for any reason other than [EIO], any file
    named by new shall be unaffected. [Option End]

RETURN VALUE

    Upon successful completion, rename() shall return 0; otherwise, -1 shall
    be returned, [CX] [Option Start] errno shall be set to indicate the error,
    [Option End] and neither the file named by old nor the file named by new
    shall be changed or created.

ERRORS

    The rename() function shall fail if:

    [EACCES]
        [CX] [Option Start] A component of either path prefix denies search
        permission; or one of the directories containing old or new denies
        write permissions; or, write permission is required and is denied for
        a directory pointed to by the old or new arguments. [Option End]
    [EBUSY]
        [CX] [Option Start] The directory named by old or new is currently in
        use by the system or another process, and the implementation considers
        this an error. [Option End]
    [EEXIST] or [ENOTEMPTY]
        [CX] [Option Start]
        The link named by new is a directory that is not an empty directory. [
        Option End]
    [EINVAL]
        [CX] [Option Start] The new directory pathname contains a path prefix
        that names the old directory. [Option End]
    [EIO]
        [CX] [Option Start] A physical I/O error has occurred. [Option End]
    [EISDIR]
        [CX] [Option Start] The new argument points to a directory and the old
        argument points to a file that is not a directory. [Option End]
    [ELOOP]
        [CX] [Option Start] A loop exists in symbolic links encountered during
        resolution of the path argument. [Option End]
    [EMLINK]
        [CX] [Option Start] The file named by old is a directory, and the link
        count of the parent directory of new would exceed {LINK_MAX}. [Option
        End]
    [ENAMETOOLONG]
        [CX] [Option Start]
        The length of the old or new argument exceeds {PATH_MAX} or a pathname
        component is longer than {NAME_MAX}. [Option End]
    [ENOENT]
        [CX] [Option Start] The link named by old does not name an existing
        file, or either old or new points to an empty string. [Option End]
    [ENOSPC]
        [CX] [Option Start] The directory that would contain new cannot be
        extended. [Option End]
    [ENOTDIR]
        [CX] [Option Start] A component of either path prefix is not a
        directory; or the old argument names a directory and new argument
        names a non-directory file. [Option End]
    [EPERM] or [EACCES]
        [XSI] [Option Start]
        The S_ISVTX flag is set on the directory containing the file referred
        to by old and the caller is not the file owner, nor is the caller the
        directory owner, nor does the caller have appropriate privileges; or
        new refers to an existing file, the S_ISVTX flag is set on the
        directory containing this file, and the caller is not the file owner,
        nor is the caller the directory owner, nor does the caller have
        appropriate privileges. [Option End]
    [EROFS]
        [CX] [Option Start] The requested operation requires writing in a
        directory on a read-only file system. [Option End]
    [EXDEV]
        [CX] [Option Start] The links named by new and old are on different
        file systems and the implementation does not support links between
        file systems. [Option End]

    The rename() function may fail if:

    [EBUSY]
        [XSI] [Option Start] The file named by the old or new arguments is a
        named STREAM. [Option End]
    [ELOOP]
        [CX] [Option Start] More than {SYMLOOP_MAX} symbolic links were
        encountered during resolution of the path argument. [Option End]
    [ENAMETOOLONG]
        [CX] [Option Start]
        As a result of encountering a symbolic link in resolution of the path
        argument, the length of the substituted pathname string exceeded {
        PATH_MAX}. [Option End]
    [ETXTBSY]
        [CX] [Option Start] The file to be renamed is a pure procedure (shared
        text) file that is being executed. [Option End]

*/

specification
IntT rename_spec( CallContext context, CString *oldPath, CString *newPath,
                 ErrorCode *errno, CancelStatus status )
{
  FileSystem *pre_fs = clone(getFileSystem(context));
  Bool3 isELOOP, isELOOP2;
  CString *absoldPath = resolvePathSymLink_Ext(context, pre_fs, oldPath, &isELOOP);
  CString *absnewPath = resolvePathSymLink_Ext(context, pre_fs, newPath, &isELOOP2);
  Bool3 isELOOPF;
  Bool3 isELOOPF2;
    CString *absoldPathFinal = clone(resolvePath_Ext(context, pre_fs, oldPath, &isELOOPF));
    CString *absnewPathFinal = clone(resolvePath_Ext(context, pre_fs, newPath, &isELOOPF2));

  pre
  {
    return true;
  }
  post
  {
    FileSystem *file_system = getFileSystem(context);
    File *parent_directory = getFile_FileSystem(pre_fs, getParentDir_Path(absnewPath));

    DUMP("rename_spec($(obj),$(obj)) returns $(obj), errno=$(obj)\n", oldPath, newPath,
          create_Integer(rename_spec), create_ErrorCode(*errno));

   /*
    * [when error occured], -1 shall be returned, [CX] errno shall
    * be set to indicate the error,
    */
    ERROR_BEGIN(POSIX_RENAME, "rename.22",  -1 == rename_spec,  *errno )
    /*
     * The rename() function shall fail if:
     * [EACCES]
     * [CX] A component of either path prefix denies search permission; or one of the
     * directories containing old or new denies write permissions; or, write
     * permission is required and is denied for a directory pointed to by the old or
     * new arguments.
     *
     */
        ERROR_SHALL3(POSIX_RENAME, EACCES, "rename.90.01",
            or_Bool3(isEACCES_dir_mkrm(context, pre_fs, oldPath),
                isEACCES_dir_mkrm(context, pre_fs, newPath)))
     /*
     * The rename() function shall fail if:
     * [EBUSY]
     * [CX] The directory named by old or new is currently in use by the system or
     * another process, and the implementation considers this an error.
     *
     */
        ERROR_SHALL(POSIX_RENAME, EBUSY, "rename.90.02", TODO_ERR(EBUSY))
     /*
     * The rename() function shall fail if:
     * [EEXIST] or [ENOTEMPTY]
     * [CX] The link named by new is a directory that is not an empty directory.
     */
        ERROR_SHALL3(POSIX_RENAME, EEXIST, "rename.90.03",
            False_Bool3==isEmpty_Directory(pre_fs, getFile_FileSystem(pre_fs, newPath)))
     /*
     * The rename() function shall fail if:
     * [EINVAL]
     * [CX] The new directory pathname contains a path prefix that names the old
     * directory.
     */
        ERROR_SHALL(POSIX_RENAME, EINVAL, "rename.90.04", TODO_ERR(EINVAL))    // TODO: This SHOULD be done
     /*
     * The rename() function shall fail if:
     * [EIO]
     * [CX] A physical I/O error has occurred.
     */
        ERROR_SHALL(POSIX_RENAME, EIO, "rename.90.05", TODO_ERR(EIO))
     /*
     * The rename() function shall fail if:
     * [EISDIR]
     * [CX] The new argument points to a directory and the old argument points to a
     * file that is not a directory.
     */
        ERROR_SHALL3(POSIX_RENAME, EISDIR, "rename.90.06", and_Bool3(
            isFileKindOf_FileSystem(pre_fs, newPath, DirectoryFile),
            not_Bool3(isFileKindOf_FileSystem(pre_fs,oldPath,DirectoryFile))
        ))
     /*
     * The rename() function shall fail if:
     * [ELOOP]
     * [CX] A loop exists in symbolic links encountered during resolution of the path
     * argument.
     */
        ERROR_SHALL3(POSIX_RENAME, ELOOP, "rename.90.07", or_Bool3(isELOOP,isELOOP2))
     /*
     * The rename() function shall fail if:
     * [EMLINK]
     * [CX] The file named by old is a directory, and the link count of the parent
     * directory of new would exceed {LINK_MAX}.
     */
        ERROR_SHALL(POSIX_RENAME, EMLINK, "rename.90.08", TODO_ERR(EMLINK))
     /*
     * The rename() function shall fail if:
     * [ENAMETOOLONG]
     * [CX] The length of the old or new argument exceeds {PATH_MAX} or a pathname
     * component is longer than {NAME_MAX}.
     */
        ERROR_SHALL3(POSIX_RENAME, ENAMETOOLONG, "rename.90.09", or_Bool3(isENAMETOOLONG(context, oldPath),
        isENAMETOOLONG(context,newPath)))
     /*
     * The rename() function shall fail if:
     * [ENOENT]
     * [CX] The link named by old does not name an existing file, or either old or
     * new points to an empty string.
     */
        ERROR_SHALL3(POSIX_RENAME, ENOENT, "rename.90.10", or_Bool3(isENOENT_dir(context, pre_fs, absoldPath),
            length_CString(newPath)==0?True_Bool3:False_Bool3))
     /*
     * The rename() function shall fail if:
     * [ENOSPC]
     * [CX] The directory that would contain new cannot be extended.
     */
        ERROR_SHALL(POSIX_RENAME, ENOSPC, "rename.90.11", TODO_ERR(ENOSPC))
     /*
     * The rename() function shall fail if:
     * [ENOTDIR]
     * [CX] A component of either path prefix is not a directory; or the old argument
     * names a directory and new argument names a non-directory file.
     */
        ERROR_SHALL3(POSIX_RENAME, ENOTDIR, "rename.90.12",
        or_Bool3(
            or_Bool3(
                isENOTDIR_dir(context, pre_fs, getParentDir_Path(absoldPath)),
                isENOTDIR_dir(context, pre_fs, getParentDir_Path(absnewPath))
            ),
            and_Bool3(
                isFileKindOf_FileSystem(pre_fs, absoldPath, DirectoryFile),
                not_Bool3(isFileKindOf_FileSystem(pre_fs, absnewPath, DirectoryFile))
            )
        ))
     /*
     * The rename() function shall fail if:
     * [EPERM] or [EACCES]
     * [XSI] The S_ISVTX flag is set on the directory containing the file referred to
     * by old and the caller is not the file owner, nor is the caller the directory
     * owner, nor does the caller have appropriate privileges; or new refers to an
     * existing file, the S_ISVTX flag is set on the directory containing this file,
     * and the caller is not the file owner, nor is the caller the directory owner,
     * nor does the caller have appropriate privileges.
     */
        ERROR_SHALL(POSIX_RENAME, EPERM, "rename.90.13", TODO_ERR(EPERM))
     /*
     * The rename() function shall fail if:
     * [EROFS]
     * [CX] The requested operation requires writing in a directory on a read-only
     * file system.
     */
        ERROR_SHALL(POSIX_RENAME, EROFS, "rename.90.14", TODO_ERR(EROFS))
     /*
     * The rename() function shall fail if:
     * [EXDEV]
     * [CX] The links named by new and old are on different file systems and the
     * implementation does not support links between file systems.
     */
        ERROR_SHALL(POSIX_RENAME, EXDEV, "rename.90.15", TODO_ERR(EXDEV))
     /*
     * The rename() function may fail if:
     * [EBUSY]
     * [XSI] The file named by the old or new arguments is a named STREAM.
     */
        ERROR_MAY(POSIX_RENAME, EBUSY, "rename.91.01", TODO_ERR(EBUSY))
     /*
     * The rename() function may fail if:
     * [ELOOP]
     * [CX] More than {SYMLOOP_MAX} symbolic links were encountered during resolution
     * of the path argument.
     */
        ERROR_MAY3(POSIX_RENAME, ELOOP, "rename.91.02", or_Bool3(isELOOP,isELOOP2))
     /*
     * The rename() function may fail if:
     * [ENAMETOOLONG]
     * [CX] As a result of encountering a symbolic link in resolution of the path
     * argument, the length of the substituted pathname string exceeded {PATH_MAX}.
     */
        ERROR_MAY(POSIX_RENAME, ENAMETOOLONG, "rename.91.03", TODO_ERR(ENAMETOOLONG))
     /*
     * The rename() function may fail if:
     * [ETXTBSY]
     * [CX] The file to be renamed is a pure procedure (shared text) file that is
     * being executed.
     */
        ERROR_MAY(POSIX_RENAME, ETXTBSY, "rename.91.04", TODO_ERR(ETXTBSY))

    ERROR_END()

    /*
     * Upon successful completion, rename() shall return 0;
     *
     */
    REQ("rename.21", "Upon successful completion, rename() shall return 0",
        0 == rename_spec);

    /*
     * The rename() function shall change the name of a file
     *
     */
        {
            File *oldF = getFile_FileSystem(file_system, absoldPath);
            File *newF = getFile_FileSystem(file_system, absnewPath);
            REQ("rename.01",
                "The rename() function shall change the name of a file",
                NULL==oldF &&    NULL!=newF);
        }

     /*
     * If either the old or new argument names a symbolic link, rename() shall operate
     * on the symbolic link itself, and shall not resolve the last component of the
     * argument.
     *
     */
    REQ("rename.02", "", TODO_REQ());
     /*
     * If the old argument and the new argument resolve to the same existing file,
     * rename() shall return successfully and perform no other action
     *
     */
    REQ("rename.03", "", TODO_REQ());
     /*
     * If the old argument points to the pathname of a file that is not a directory,
     * the new argument shall not point to the pathname of a directory
     *
     */
    REQ("rename.04",
        "If the old argument points to the pathname of a file that is not a "
        "directory",
        !(False_Bool3==isFileKindOf_FileSystem(pre_fs, absoldPath, DirectoryFile)
        && True_Bool3==isFileKindOf_FileSystem(pre_fs, absnewPath, DirectoryFile))
        );
     /*
     * If the link named by the new argument exists, it shall be removed and old
     * renamed to new
     *
     */
    REQ("rename.05", "", TODO_REQ());
     /*
     * In this case, a link named new shall remain visible to other processes
     * throughout the renaming operation and refer either to the file referred to by
     * new or old before the operation began
     *
     */
    REQ("rename.06", "", TODO_REQ());
     /*
     * Write access permission is required for both the directory containing old and
     * the directory containing new
     *
     */
    REQ("rename.07", "", TODO_REQ());
     /*
     * If the old argument points to the pathname of a directory, the new argument
     * shall not point to the pathname of a file that is not a directory
     *
     */
    REQ("rename.08", "", TODO_REQ());
     /*
     * If the directory named by the new argument exists, it shall be removed and old
     * renamed to new.
     *
     */
    REQ("rename.09", "", TODO_REQ());
     /*
     * If new names an existing directory, it shall be required to be an empty
     * directory.
     *
     */
    REQ("rename.10", "", TODO_REQ());
     /*
     * If the old argument points to a pathname of a symbolic link, the symbolic link
     * shall be renamed
     *
     */
    REQ("rename.11", "", TODO_REQ());
     /*
     * If the new argument points to a pathname of a symbolic link, the symbolic link
     * shall be removed
     *
     */
    REQ("rename.12", "", TODO_REQ());
     /*
     * The new pathname shall not contain a path prefix that names old
     *
     */
    REQ("rename.13", "", TODO_REQ());
     /*
     * Write access permission is required for the directory containing old and the
     * directory containing new
     *
     */
    REQ("rename.14", "", TODO_REQ());
     /*
     * If the old argument points to the pathname of a directory, write access
     * permission may be required for the directory named by old
     *
     */
    REQ("rename.15", "", TODO_REQ());
     /*
     * and, if it exists, the directory named by new.
     *
     */
    REQ("rename.16", "", TODO_REQ());
     /*
     * If the link named by the new argument exists and the file's link count becomes
     * 0 when it is removed and no process has the file open, the space occupied by
     * the file shall be freed and the file shall no longer be accessible
     *
     */
    REQ("rename.17", "", TODO_REQ());
     /*
     * If one or more processes have the file open when the last link is removed, the
     * link shall be removed before rename() returns, but the removal of the file
     * contents shall be postponed until all references to the file are closed
     *
     */
    REQ("rename.18", "", TODO_REQ());
     /*
     * Upon successful completion, rename() shall mark for update the st_ctime and
     * st_mtime fields of the parent directory of each file
     *
     */
    REQ("rename.19", "", !parent_directory->ctime_updated &&
                !parent_directory->mtime_updated);
     /*
     * If the rename() function fails for any reason other than [EIO], any file named
     * by new shall be unaffected
     *
     */
    REQ("rename.20", "", TODO_REQ());

     /*
     * In this case [If the directory named by the new argument exists], a
     * link named new shall exist throughout the renaming operation
     * and shall refer either to the directory referred to by new or old
     * before the operation began
     */
    REQ("rename.23", "", TODO_REQ());
     /*
     * and neither the file named by old nor the file named by new shall be changed or
     * created
     *
     */
    REQ("rename.24", "", TODO_REQ());
    return true;
  }
}

void on_rename( CallContext context, CString *oldPath, CString *newPath, IntT retval, ErrorCode *errno)
{
    FileSystem* file_system = getFileSystem(context);
    Bool3 isELOOP;
    CString *absoldPath = resolvePathSymLink_Ext(context, file_system, oldPath, &isELOOP);
    CString *absnewPath = resolvePathSymLink_Ext(context, file_system, newPath, &isELOOP);

    if (retval == 0)
    {
        rename_FileSystem(file_system, absoldPath, absnewPath);
    }
}

void rename_FileSystem(FileSystem *fs, CString *oldPath, CString *newPath)
{
    File *oldFile = getFile_FileSystem(fs, oldPath);
    File *newFile = getFile_FileSystem(fs, newPath);

    if(NULL!=newFile && newFile->kind == DirectoryFile
        && True_Bool3!=isEmpty_Directory(fs, newFile))
        return;    // renaming to the nonempty directory is prohibited

    if(NULL!=newFile)// unlink newFile if necessery
    {
        newFile = unlink_FileSystem(fs, newPath, false);
    }

    unlink_FileSystem(fs, oldPath, true);
    linkFile_FileSystem(fs, newPath, oldFile);    // link newPath to file
}

/*
Linux Standard Base Core Specification 3.1
Copyright (c) 2004, 2005 Free Standards Group

NAME

    unlink -- remove a directory entry

SYNOPSIS

    int unlink(const char * path);

DESCRIPTION

    unlink() is as specified in ISO POSIX (2003), but with differences as listed
    below.

    See also Section 18.1, Additional behaviors: unlink/link on directory.

    May return EISDIR on directories

    If path specifies a directory, the implementation may return EISDIR instead
    of EPERM as specified by ISO POSIX (2003).

    Rationale: The Linux kernel has deliberately chosen EISDIR for this case and
    does not expect to change.
*/
/*
Linux Standard Base Core Specification 3.1
Copyright (c) 2004, 2005 Free Standards Group

    refers

The Open Group Base Specifications Issue 6
IEEE Std 1003.1, 2004 Edition
Copyright (c) 2001-2004 The IEEE and The Open Group, All Rights reserved.

NAME

unlink - remove a directory entry

SYNOPSIS

#include <unistd.h>

int unlink(const char *path);

DESCRIPTION

The unlink() function shall remove a link to a file. If path names a symbolic
link, unlink() shall remove the symbolic link named by path and shall not
affect any file or directory named by the contents of the symbolic link.
Otherwise, unlink() shall remove the link named by the pathname pointed to by
path and shall decrement the link count of the file referenced by the link.

When the file's link count becomes 0 and no process has the file open, the
space occupied by the file shall be freed and the file shall no longer be
accessible. If one or more processes have the file open when the last link is
removed, the link shall be removed before unlink() returns, but the removal of
the file contents shall be postponed until all references to the file are
closed.

The path argument shall not name a directory unless the process has appropriate
privileges and the implementation supports using unlink() on directories.

Upon successful completion, unlink() shall mark for update the st_ctime and
st_mtime fields of the parent directory. Also, if the file's link count is
not 0, the st_ctime field of the file shall be marked for update.

RETURN VALUE

Upon successful completion, 0 shall be returned. Otherwise, -1 shall be
returned and errno set to indicate the error. If -1 is returned, the named
file shall not be changed.

ERRORS

style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:


[EACCES]

Search permission is denied for a component of the path prefix, or write
permission is denied on the directory containing the directory entry to be
removed.

[EBUSY]

The file named by the path argument cannot be unlinked because it is being used
by the system or another process and the implementation considers this an
error.

[ELOOP]

A loop exists in symbolic links encountered during resolution of the path
argument.

[ENAMETOOLONG]

The length of the path argument exceeds {PATH_MAX} or a pathname component is
longer than {NAME_MAX}.

[ENOENT]

A component of path does not name an existing file or path is an empty string.

[ENOTDIR]

A component of the path prefix is not a directory.

[EPERM]

The file named by path is a directory, and either the calling process does not
have appropriate privileges, or the implementation prohibits using unlink() on
directories.

[EPERM] or [EACCES]

[XSI] The S_ISVTX flag is set on the directory containing the file referred
to by the path argument and the caller is not the file owner, nor is the
caller the directory owner, nor does the caller have appropriate privileges.

[EROFS]

The directory entry to be unlinked is part of a read-only file system.

style="background-color: khaki;" name="anch.unlink.92;remove.92" href="#
topanch.unlink.92;remove.92">unlink.92;remove.92 : MAY:POSIX_UNLINK;
POSIX_REMOVE} The unlink() function may fail and not unlink the file if:

[EBUSY]

[XSI] The file named by path is a named STREAM.

[ELOOP]

More than {SYMLOOP_MAX} symbolic links were encountered during resolution of
the path argument.

[ENAMETOOLONG]

As a result of encountering a symbolic link in resolution of the path argument,
the length of the substituted pathname string exceeded {PATH_MAX}.

[ETXTBSY]

The entry to be unlinked is the last directory entry to a pure procedure
(shared text) file that is being executed.
*/
specification
IntT unlink_spec( CallContext context, CString *path, ErrorCode *errno, CancelStatus status )
{
  FileSystem *pre_fs = clone(getFileSystem(context));
  Bool3 isELOOP;
  CString *abspath = resolvePathSymLink_Ext(context, pre_fs, path, &isELOOP);

  pre
  {
    return true;
  }
  post
  {
      File *old_link = getFile_FileSystem(pre_fs, abspath);
      File *old_dest = getSymLink_File(context, pre_fs, old_link, &isELOOP);
      FileSystem *file_system = getFileSystem(context);
      File *new_link= getFile_FileSystem(file_system, abspath);
      File *new_dest = getSymLink_File(context, file_system, new_link, &isELOOP);
      Bool3 isSymLink = isFileKindOf_FileSystem(pre_fs, abspath, SymbolicLinkFile);
      NLinkTObj *nlinks=NULL;
      File *parent_directory = getFile_FileSystem(pre_fs, getParentDir_Path(abspath));

      DUMP("unlink_spec($(obj)) returns $(obj), errno=$(obj)\n", path,
          create_Integer(unlink_spec), create_ErrorCode(*errno));

        ERROR_BEGIN(POSIX_UNLINK, "unlink.14", -1 == unlink_spec,  *errno)

            /*
             *  The unlink() function shall fail and shall not unlink the file if:
             *
             * [EACCES]
             * Search permission is denied for a component of the path prefix, or write
             * permission is denied on the directory containing the directory entry to be
             * removed.
             */
            /*
             * The unlink() function shall fail and shall not unlink the file if:
             *
             * [EPERM] or [EACCES]
             * [XSI] The S_ISVTX flag is set on the directory containing the file referred
             * to by the path argument and the caller is not the file owner, nor is the
             * caller the directory owner, nor does the caller have appropriate privileges.
             */
            ERROR_SHALL3(POSIX_UNLINK, EACCES, "unlink.90.01;unlink.90.08",
                           isEACCES_dir_mkrm(context, file_system, path)
                        )

            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [EBUSY]
             * The file named by the path argument cannot be unlinked because it is being used
             * by the system or another process and the implementation considers this an
             * error.
             */
            ERROR_SHALL(POSIX_UNLINK, EBUSY, "unlink.90.02", TODO_ERR(EBUSY) )

            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [ELOOP]
             * A loop exists in symbolic links encountered during resolution of the path
             * argument.
             */
            ERROR_SHALL3(POSIX_UNLINK, ELOOP, "unlink.90.03", isELOOP )

            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [ENAMETOOLONG]
             * The length of the path argument exceeds {PATH_MAX} or a pathname component is
             * longer than {NAME_MAX}.
             */
            ERROR_SHALL3(POSIX_UNLINK, ENAMETOOLONG, "unlink.90.04", isENAMETOOLONG(context, path) )

            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [ENOENT]
             * A component of path does not name an existing file or path is an empty string.
             */
            ERROR_SHALL3(POSIX_UNLINK, ENOENT, "unlink.90.05", isENOENT_dir(context, pre_fs, abspath) )

            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [ENOTDIR]
             * A component of the path prefix is not a directory.
             */
            ERROR_SHALL3(POSIX_UNLINK, ENOTDIR, "unlink.90.06", isENOTDIR_dir(context, pre_fs, getParentDir_Path(abspath)) )

            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [EPERM]
             * The file named by path is a directory, and either the calling process does not
             * have appropriate privileges, or the implementation prohibits using unlink() on
             * directories.
             */
            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [EPERM] or [EACCES]
             * [XSI] The S_ISVTX flag is set on the directory containing the file referred
             * to by the path argument and the caller is not the file owner, nor is the
             * caller the directory owner, nor does the caller have appropriate privileges.
             */
            ERROR_SHALL3(POSIX_UNLINK, EPERM, "unlink.90.07;unlink.90.08",
                        or_Bool3(isEPERM_unlink(context, pre_fs, abspath),
                        POSIX_OPTION(context, XSI)?isEPERM_rmdir(context, pre_fs, abspath):False_Bool3)
                        )
            /*
             * If path specifies a directory, the implementation may return EISDIR instead of
             * EPERM as specified by ISO POSIX (2003).
             */
            ERROR_SHALL( LSB_UNLINK, EISDIR, "unlink.90.30", TODO_ERR(EISDIR) );

            /*
             * style="background-color: khaki;" name="anch.unlink.90;remove.90" href="#
             * topanch.unlink.90;remove.90">unlink.90;remove.90 : SHALL:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function shall fail and shall not unlink the file if:
             *
             * [EROFS]
             * The directory entry to be unlinked is part of a read-only file system.
             */
            ERROR_SHALL(POSIX_UNLINK, EROFS, "unlink.90.09", TODO_ERR(EROFS) )

            /*
             * style="background-color: khaki;" name="anch.unlink.92;remove.92" href="#
             * topanch.unlink.92;remove.92">unlink.92;remove.92 : MAY:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function may fail and not unlink the file if:
             *
             * [EBUSY]
             * [XSI] The file named by path is a named STREAM.
             */
            ERROR_MAY(POSIX_UNLINK, EBUSY, "unlink.92.01", TODO_ERR(EBUSY))

            /*
             * style="background-color: khaki;" name="anch.unlink.92;remove.92" href="#
             * topanch.unlink.92;remove.92">unlink.92;remove.92 : MAY:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function may fail and not unlink the file if:
             *
             * [ELOOP]
             * More than {SYMLOOP_MAX} symbolic links were encountered during resolution of
             * the path argument.
             */
            ERROR_MAY3(POSIX_UNLINK, ELOOP, "unlink.92.02", isELOOP)

            /*
             * style="background-color: khaki;" name="anch.unlink.92;remove.92" href="#
             * topanch.unlink.92;remove.92">unlink.92;remove.92 : MAY:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function may fail and not unlink the file if:
             *
             * [ENAMETOOLONG]
             * As a result of encountering a symbolic link in resolution of the path argument,
             * the length of the substituted pathname string exceeded {PATH_MAX}.
             */
            ERROR_MAY3(POSIX_UNLINK, ENAMETOOLONG, "unlink.92.03", isENAMETOOLONG(context, abspath))

            /*
             * style="background-color: khaki;" name="anch.unlink.92;remove.92" href="#
             * topanch.unlink.92;remove.92">unlink.92;remove.92 : MAY:POSIX_UNLINK;
             * POSIX_REMOVE} The unlink() function may fail and not unlink the file if:
             *
             * [ETXTBSY]
             * The entry to be unlinked is the last directory entry to a pure procedure
             * (shared text) file that is being executed.
             */
            ERROR_MAY(POSIX_UNLINK, ETXTBSY, "unlink.92.04", TODO_ERR(ETXTBSY))

        ERROR_END()
        /*
         * The unlink() function shall remove a link to a file
         */
        REQ("unlink.05",
            "The unlink() function shall remove a link to a file",
            new_link==NULL);

        /*
         * If path names a symbolic link, unlink() shall remove the symbolic link named
         * by path and shall not affect any file or directory named by the contents of the
         * symbolic link
         */
        REQ("unlink.06",
            "If path names a symbolic link, unlink() shall remove the "
            "symbolic link named by path",
            True_Bool3!=isSymLink
            || new_dest==old_dest);

        /*
         * Otherwise, unlink() shall remove the link named by the pathname pointed to by
         * path and shall decrement the link count of the file referenced by the link
         */
        REQ("unlink.07",
            "Otherwise, unlink() shall remove the link named by the pathname "
            "pointed to by path",
            False_Bool3!=isSymLink
            || NULL==(nlinks=diffLinksCount_File(old_dest, new_dest))
            || *nlinks==-1);

        /*
         * When the file's link count becomes 0 and no process has the file open, the
         * space occupied by the file shall be freed and the file shall no longer be
         * accessible
         */
        REQ("unlink.08",
            "When the file's link count becomes 0 and no process has the "
            "file open",
            nlinks==NULL || *nlinks!=0 || new_dest==NULL);

        /*
         * If one or more processes have the file open when the last link is removed, the
         * link shall be removed before unlink() returns, but the removal of the file
         * contents shall be postponed until all references to the file are closed.
         */
        REQ("unlink.09", "", TODO_REQ());

        /*
         * The path argument shall not name a directory unless the process has appropriate
         * privileges and the implementation supports using unlink() on directories
         */
        // TODO: appropriate privileges
        REQ("unlink.10",
            "The path argument shall not name a directory unless the process "
            "has appropriate privileges",
            True_Bool3!=isFileKindOf_FileSystem(pre_fs, abspath, DirectoryFile));


        /*
         * Upon successful completion, unlink() shall mark for update the st_ctime and
         * st_mtime fields of the parent directory
         */
        REQ("unlink.11",
            "Upon successful completion, unlink() shall mark for update the "
            "st_ctime and st_mtime fields of the parent directory",
            !parent_directory->ctime_updated &&
            !parent_directory->mtime_updated);

        /*
         * Also, if the file's link count is not 0, the st_ctime field of the file shall
         * be marked for update
         */
        REQ("unlink.12",
            "if the file's link count is not 0",
            nlinks==NULL || *nlinks!=0 || new_dest==NULL || !new_dest->ctime_updated);

        /*
         * Upon successful completion, 0 shall be returned
         */
        REQ("unlink.13",
            "Upon successful completion, 0 shall be returned",
            0==unlink_spec);

        /*
         * If -1 is returned, the named file shall not be changed
         */
        REQ("unlink.15", "", TODO_REQ());

    return true;
  }
}

void on_unlink( CallContext context, CString *path, IntT retval, ErrorCode *errno )
{
    FileSystem* file_system = getFileSystem(context);
    Bool3 isELOOP;
    CString *abspath = resolvePath_Ext(context, file_system, path, &isELOOP);

    if (retval == 0)
    {
        unlink_FileSystem(file_system, abspath, false);
    }
}

File *unlink_FileSystem(FileSystem *fs, CString *path, bool keepFile)
{
    File *file = fileExist_FileSystem(fs, path, UnknownFileKind);
    File *dir = fileExist_FileSystem(fs, getParentDir_Path(path), DirectoryFile);
    CString *basename = getBaseName_Path(path);
    FileIdObj *destFileId = getDirLink_Directory(dir, basename);
    File *destFile;
    NLinkTObj *nlink;

    assertion(destFileId!=NULL, "unlink_FileSystem: link to nowhere");

    destFile = getFile_FileId_Ext(fs, *destFileId);
    assertion(destFile!=NULL, "unlink_FileSystem: link to nowhere");

    unregisterDirLink_Directory(dir, basename);
    unregisterUpwardLink_File(destFile,dir, basename);

    nlink = incLinksCount_File(fs, destFile, (NLinkT) -1);
    if(NULL!=nlink && 0 == *nlink && !keepFile)
    {
        unregisterFile(fs, destFile);
        return NULL;
    }
    return destFile;
}


/********************************************************************/
/**                       Helper Functions                         **/
/********************************************************************/
void updateAtimePath(CallContext context, FileSystem* fs, CString* path)
{
    CString* resolved;
    Bool3 eloop;
    File* file;

    if(fs==NULL || path==NULL)
    {
        resetTimeFlags_FileSystem(fs);
        return;
    }

    resolved=resolvePath_Ext(context, fs, path, &eloop);

    if(resolved==NULL)
    {
        resetTimeFlags_FileSystem(fs);
        return;
    }

    file=getFile_FileSystem(fs, resolved);

    if(file==NULL)
        return;

    file->atime_updated=false;
}
void updateCtimePath(CallContext context, FileSystem* fs, CString* path)
{
    CString* resolved;
    Bool3 eloop;
    File* file;

    if(fs==NULL || path==NULL)
    {
        resetTimeFlags_FileSystem(fs);
        return;
    }

    resolved=resolvePath_Ext(context, fs, path, &eloop);

    if(resolved==NULL)
    {
        resetTimeFlags_FileSystem(fs);
        return;
    }

    file=getFile_FileSystem(fs, resolved);

    if(file==NULL)
        return;

    file->ctime_updated=false;
}
void updateMtimePath(CallContext context, FileSystem* fs, CString* path)
{
    CString* resolved;
    Bool3 eloop;
    File* file;

    if(fs==NULL || path==NULL)
    {
        resetTimeFlags_FileSystem(fs);
        return;
    }

    resolved=resolvePath_Ext(context, fs, path, &eloop);

    if(resolved==NULL)
    {
        resetTimeFlags_FileSystem(fs);
        return;
    }

    file=getFile_FileSystem(fs, resolved);

    if(file==NULL)
        return;

    file->mtime_updated=false;
}

void resetTimeFlags_FileSystem(FileSystem* fs)
{
    Map* mp=fs->files;
    File* tmp;
    int size=size_Map(mp), i;
    for(i=0;i<size;i++)
    {
        tmp=get_Map(mp, key_Map(mp, i));
        tmp->atime_updated=tmp->ctime_updated=tmp->mtime_updated=false;
    }

}

/********************************************************************/
/**                         File Permissions                       **/
/********************************************************************/
String* to_string_FilePermission( FilePermission* perm )
{
    if(perm==NULL)
        return create_String( "NULL");

  return format_String( "%c%c%c", perm->read ? 'r' : '-', perm->write ? 'w' : '-', perm->execute ? 'x' : '-' );
}
specification typedef struct FilePermission FilePermission = {

  .to_string = (ToString)to_string_FilePermission

};
FilePermission* create_FilePermission( bool read, bool write, bool execute)
{
  return create( &type_FilePermission, read, write, execute);
}

String* to_string_FilePermissions( struct FilePermissions *perms )
{
  return concat_String( to_string_FilePermission(perms->owner),
                        concat_String( to_string_FilePermission(perms->group),
                                       to_string_FilePermission(perms->other)
                                     )
                      );
}

/* File permissions decoding from string form */
FilePermission* create_FilePerm(const char *permstr)
{
    FilePermission* perm = create_FilePermission(false,false,false);

    assertion(permstr[0]&&permstr[1]&&permstr[2], "createFilePerm: string perm should be in form rwx");
    if(permstr[0]=='r')
        perm->read=true;
    if(permstr[1]=='w')
        perm->write=true;
    if(permstr[2]=='x')
        perm->execute=true;

    return perm;
}

FilePermissions *create_FilePermissions_String(const char *permstr)
{
    assertion(strlen(permstr)==9, "create_FilePermissions_String: string perm should be in form rwxrwxrwx");
    return create_FilePermissions(create_FilePerm(permstr), create_FilePerm(permstr+3), create_FilePerm(permstr+6), Unknown_Bool3, Unknown_Bool3, Unknown_Bool3);
}

specification typedef struct FilePermissions FilePermissions = {

  .to_string = (ToString)to_string_FilePermissions

};

FilePermissions* create_FilePermissions( FilePermission* owner, FilePermission* group, FilePermission* other, Bool3 set_uid, Bool3 set_gid, Bool3 set_vtx )
{
  return create( &type_FilePermissions, owner, group, other, Unknown_Bool3, Unknown_Bool3, Unknown_Bool3 );
}

static String* to_string_FileKind(FileKind *kind)
{
#define trans(x) case x: return create_String(#x);
  switch (*kind)
  {
    trans(FIFOFile)
    trans(UnknownFileKind)
    trans(RegularFile)
    trans(BlockFile)
    trans(CharacterFile)
    trans(DirectoryFile)
    trans(SymbolicLinkFile)
    trans(Socket)
    trans(MessageQueue)
    trans(SemaphoreFile)
    trans(SharedMemoryObject)
    trans(TypedMemoryObject)
   }
  return toString( create_Integer( *kind ) );
}

specification typedef FileKind FileKindObj = { .to_string = (ToString)to_string_FileKind };

FileKindObj *create_FileKindObj(FileKind kind)
{
    return create(&type_FileKindObj, kind);
}


/********************************************************************/
/**                          File Status                           **/
/********************************************************************/
specification typedef struct FileStatus FileStatus = {};


/********************************************************************/
/**                          Upward Link                           **/
/********************************************************************/
specification typedef struct UpwardLink UpwardLink = {};

UpwardLink* create_UpwardLink( FileId directory, CString* filename )
{
  return create( &type_UpwardLink, directory, filename );
}



/********************************************************************/
/**                             File                               **/
/********************************************************************/
/*String* to_string_File( struct File *file)
{
//      FileId           fileid;
  // Parents
//  Set*             parents;     // UpwardLink-set
  // File meta info
//  FileKind         kind;        // Type of file
//  DevTObj*         dev;         // Device ID of device containing file
//  InoTObj*         ino;         // File serial number (dev + ino should be unique)
//  FilePermissions* permissions; // Permissions
//  UidTObj*         uid;         // User ID of file
//  GidTObj*         gid;         // Group ID of file
//  TimeSpecTObj*    atime;       // Time of last access
//  TimeSpecTObj*    mtime;       // Time of last data modification
//  TimeSpecTObj*    ctime;       // Time of last status change
//  bool             atime_update;    // atime update flag
//  bool             mtime_update;    // mtime update flag
//  bool             ctime_update;    // ctime update flag
  // File Data Descriptor
//  Object*          descriptor;

  String *res = create_String("File: (");

  String *s;
  int i;

  if(NULL==file)
    return create_String("File:NULL");

  s = format_String("kind: %d", file->kind);

  switch(file->kind)
  {
  case RegularFile: s = create_String("RegularFile");break;
  case BlockFile: s = create_String("BlockFile");break;
  case CharacterFile: s = create_String("CharacterFile");break;
  case FIFOFile:s = create_String("FIFOFile");break;
  case DirectoryFile:s = create_String("DirectoryFile");break;
  case SymbolicLinkFile:s = create_String("SymbolicLinkFile");break;
  case Socket:s = create_String("Socket");break;
  }

  res = concat_String(res, concat_String(s,create_String(" ")));

  s = format_String("update: %s%s%s",file->atime_update?"a":"",
      file->mtime_update?"m":"",
      file->ctime_update?"c":"");

  res = concat_String(res, concat_String(s,create_String(" ")));

  s = toString(file->descriptor);

  res = concat_String(res, concat_String(s,create_String(" parents (")));

  for(i=0;i<size_Set(file->parents); i++)
  {
      UpwardLink *uplink = get_Set(file->parents, i);
      s = toString(uplink->filename);

      res = concat_String(res, concat_String(s,create_String(" ")));
  }
  res = concat_String(res, create_String(") "));

  res = concat_String(res, concat_String(s,create_String(")\n")));


  return res;
}
*/
specification typedef struct File File =
{ //.to_string = (ToString)to_string_File
};

File* create_FileById( FileId fileid )
{
  File* res = create_File(
                     fileid,
                     create_Set(&type_UpwardLink),
                     UnknownFileKind,
                     NULL, // dev
                     NULL, // ino
                     NULL, // permissions
                     NULL, // nlink

                     NULL, // uid
                     NULL, // gid

                     NULL, //rdev
                     NULL, //size

                     NULL, // atime
                     NULL, // mtime
                     NULL, // ctime

                     NULL, //blksize
                     NULL, //blocks

                     false,
                     false,
                     false,
                     NULL  // descriptor
               );
  return res;
}

File* create_File(
    FileId          fileid,
    Set*            parents,
    FileKind        kind,
    DevTObj*        dev,
    InoTObj*        ino,
    FilePermissions* permissions,

    NLinkTObj*      nlink,

    UidTObj*        uid,
    GidTObj*        gid,

    DevTObj*        rdev,
    OffTObj*        size,
    TimeTObj*       atime,
    TimeTObj*       mtime,
    TimeTObj*       ctime,

    BlksizeTObj*    blksize,
    BlkcntTObj*     blocks,

    bool            atime_updated,
    bool            mtime_updated,
    bool            ctime_updated,
    Object*         descriptor
)
{
    return create(&type_File,
        fileid,
        parents,
        kind,
        dev,
        ino,
        permissions,
        nlink,
        uid,
        gid,
        rdev,
        size,
        atime,
        mtime,
        ctime,
        blksize,
        blocks,
        atime_updated,
        mtime_updated,
        ctime_updated,
        descriptor);

}

FilePermissions* getPermissions_File( File* file )
{
    assertion(file != NULL,
        "getPermissions_File: file is NULL");

    return file->permissions;
}

void setPermissions_File( File* file, FilePermissions* permissions )
{
    assertion(file != NULL,
        "setPermissions_File: file is NULL");

    file->permissions = permissions;
}

UidTObj* getUserId_File( File* file )
{
    assertion(file != NULL,
        "getUserId_File: file is NULL");

    return file->uid;
}

void setUserId_File( File* file, UidTObj* uid )
{
    assertion(file != NULL,
        "setUserId_File: file is NULL");

    file->uid = uid;
}

UidTObj* getGroupId_File( File* file )
{
    assertion(file != NULL,
        "getGroupId_File: file is NULL");

    return file->gid;
}

void setGroupId_File( File* file, GidTObj* gid )
{
    assertion(file != NULL,
        "setGroupId_File: file is NULL");

    file->gid = gid;
}

Bool3 isOfKind_File(File *file, FileKind kind)
{
    assertion(file!=NULL,"isOfKind_File: file == NULL");
    if(file->kind == UnknownFileKind)
        return Unknown_Bool3;
    return file->kind == kind ? True_Bool3 : False_Bool3;
}



/********************************************************************/
/**                         Directory Entry                        **/
/********************************************************************/
/*
specification typedef struct DirectoryEntry DirectoryEntry = {};

DirectoryEntry* create_DirectoryEntry( CString* filename, FileId fileid )
{
  return create( &type_DirectoryEntry, filename, fileid );
}
*/



/********************************************************************/
/**                     Directory File Descriptor                  **/
/********************************************************************/
/*
String* to_string_DirectoryDescriptor( struct DirectoryDescriptor *dd)
{
    String *res = format_String(dd->uptodate?"DirectoryDescriptor: uptodate (":"DirectoryDescriptor: not-uptodate(");
    int i;
    for(i=0; i<size_Map(dd->files); i++)
    {
        if(i>0)
            res = concat_String(res, create_String(","));
        res = concat_String(res, toString(key_Map(dd->files, i)));
    }
    res = concat_String(res, create_String(")"));
    return res;
}
*/
specification typedef struct DirectoryDescriptor DirectoryDescriptor =
{ //.to_string = (ToString)to_string_DirectoryDescriptor
};

DirectoryDescriptor* create_DirectoryDescriptor(void)
{
  return create( &type_DirectoryDescriptor, create_Map( &type_CString, &type_FileIdObj ), false, false );
}


/********************************************************************/
/**                          File System                           **/
/********************************************************************/
specification typedef struct FileSystem FileSystem = {};

FileSystem* create_FileSystem(
    FileId     root,
    Map*       files,
    Identifier counter
    )
{
    return create(&type_FileSystem,
        root,
        files,
        counter
        );
}

FileSystem* create_FileSystemBySystemId(SystemId systemid)
{
File* root;
FileSystem* res;

  res = create_FileSystem(
                    create_FileId(systemid,0), // Root FileId
                    create_Map( &type_FileIdObj, &type_File ),
                    1
              );
  root = create_FileById(res->root);
  root->kind = DirectoryFile;
  root->descriptor = create_DirectoryDescriptor();
  put_Map( res->files, create_FileIdObj(res->root), root );
  return res;
}

FileSystem* findFileSystem( Map* file_systems, CallContext context)
{
  return get_Map( file_systems, create_SystemIdObj(context.system) );
}

FileSystem* findFileSystem_SystemId( Map* file_systems, SystemId systemid )
{
  return get_Map( file_systems, create_SystemIdObj(systemid) );
}

static
File* create_NewFileInFileSystem( FileSystem* file_system )
{
File* file = create_FileById( create_FileId( file_system->root.system, file_system->counter++ ) );

  put_Map( file_system->files, create_FileIdObj(file->fileid), file );
  return file;
}

static
File* registerFile_Directory( File* new_file, File* directory, CString* filename )
{
DirectoryDescriptor* dd;

  if (directory->kind!=DirectoryFile)
    return NULL;
  dd = directory->descriptor;

  put_Map( dd->files, filename, create_FileIdObj(new_file->fileid)  );
  dd->modified = true;
  add_Set( new_file->parents, create_UpwardLink( directory->fileid, filename ) );

  return new_file;
}

static
void registerUpwardLink_File(File *file, File *directory, CString *filename)
{
    add_Set(file->parents, create_UpwardLink(directory->fileid, filename));
}

static
void unregisterUpwardLink_File(File *file, File *directory, CString *filename)
{
    remove_Set(file->parents, create_UpwardLink(directory->fileid, filename));
}

static void registerDirLink_Directory(File *target, File *directory, CString *filename)
{
DirectoryDescriptor* dd;
  if (directory->kind!=DirectoryFile)
    return;
  dd = directory->descriptor;

  put_Map( dd->files, filename, create_FileIdObj(target->fileid)  );
  dd->modified = true;
}

static FileIdObj *getDirLink_Directory(File *directory, CString *filename)
{

DirectoryDescriptor* dd;
FileIdObj *fid = NULL;
  if (directory->kind!=DirectoryFile)
    return NULL;
  dd = directory->descriptor;

  fid = get_Map( dd->files, filename);
  return fid;
}

static void unregisterDirLink_Directory( File *directory, CString *filename)
{
DirectoryDescriptor* dd;
  if (directory->kind!=DirectoryFile)
    return;
  dd = directory->descriptor;

  remove_Map( dd->files, filename);
  dd->modified = true;
}

File *getTheOnlyParentDirectory_File(FileSystem *file_system, File *file)
{
    UpwardLink *uplink;
    int nlinks = getUpwardLinksCount_File(file);
    assertion( nlinks<= 1, "getTheOnlyParentDirectory_File: uplinks count > 1");
    uplink = nlinks > 0 ? getUpwardLinkAt_File(file, 0) : NULL;
    return uplink ? getParentDirectory_UpwardLink(file_system, uplink) : NULL;
}

static void create_DirectoryLinks(FileSystem *file_system, File *dir)
{
    File *parentdir = getTheOnlyParentDirectory_File(file_system, dir);

    registerDirLink_Directory(dir, dir, create_CString("."));
    if(NULL!=parentdir)
    {
        registerDirLink_Directory(parentdir, dir, create_CString(".."));
    }
}

File* registerFileInDir( FileSystem* file_system, File *directory, CString *filename)
{
    return registerFile_Directory(create_NewFileInFileSystem(file_system), directory, filename);
}

File* registerFile_Kind( FileSystem *file_system, CString *path, FileKind kind)
{
    File *file;
    switch(kind)
    {
    case DirectoryFile: file = registerDirectory(file_system, path);break;
    case SymbolicLinkFile: file = registerSymLink(file_system, path, NULL);break;
    case RegularFile: file = registerFile(file_system, path); file->kind = RegularFile; break;
    case FIFOFile: file = registerFile(file_system, path); file->kind = FIFOFile; break;
    default: file = registerFile(file_system, path);break;
    }
    return file;
}

void setKind_File(FileSystem *file_system, File *file, FileKind kind)
{
    switch(kind)
    {
    case DirectoryFile: setDirectoryFile_File(file_system, file);break;
    case SymbolicLinkFile: setSymLink_File(file_system, file, NULL);break;
    default: file->kind = kind;
    }
}

Bool3 isFileKindOf_FileSystem(FileSystem *fs, CString *path, FileKind kind)
{
    File *file;

    file = getFile_FileSystem(fs, path);

    if(NULL==file || kind==UnknownFileKind || file->kind==UnknownFileKind)
        return Unknown_Bool3;

    return file->kind == kind ? True_Bool3 : False_Bool3;
}

File* registerFile_FileStatus(FileSystem *file_system, CString *path, FileStatus *status)
{
    File *file;

    assertion(file_system != NULL, "registerFile_FileStatus: file_system is NULL");
    assertion(status != NULL, "registerFile_FileStatus: status is NULL");

    file = registerFile_Kind(file_system, path, status->kind);

    file->kind          = status->kind;
    file->dev           = create_DevTObj(status->dev);
    file->ino           = create_InoTObj(status->ino);
    file->permissions   = clone(status->permissions);
    file->nlink         = create_NLinkTObj(status->nlink);
    file->uid           = create_UidTObj(status->uid);
    file->gid           = create_GidTObj(status->gid);
    file->rdev          = create_DevTObj(status->rdev);
    file->size          = create_OffTObj(status->size);
    file->atime         = create_TimeTObj(status->atime);
    file->mtime         = create_TimeTObj(status->mtime);
    file->ctime         = create_TimeTObj(status->ctime);
    file->blksize       = create_BlksizeTObj(status->blksize);
    file->blocks        = create_BlkcntTObj(status->blocks);

    return file;
}

File* registerFile( FileSystem* file_system, CString* path )
{
CharT *cstr,*next;
File* directory;
File* new_directory;

  if (path == NULL)
   {// Registration of an unnamed file
    return create_NewFileInFileSystem(file_system);
   }

  cstr = toCharArray_CString(path);

  assertion( cstr[0] == '/', "registerFile: full file name expected %s", cstr );
  assertion( cstr[1] != 0, "registerFile: cannot add root" );

  directory = get_Map( file_system->files, create_FileIdObj(file_system->root) );
  cstr = cstr + 1;

  for(;;)
   {
    next = strchr( cstr, '/' );
    if (next == NULL)
     {
      return registerFile_Directory( create_NewFileInFileSystem(file_system),
                                     directory,
                                     create_CString(cstr)
                                   );
     }
    new_directory = getFile_Directory_Ext( file_system, directory, substring_CString( create_CString(cstr), 0, next - cstr ) );
    if (new_directory == NULL)
     {
      new_directory = registerFile_Directory( create_NewFileInFileSystem(file_system),
                                              directory,
                                              substring_CString( create_CString(cstr), 0, next - cstr )
                                            );
      new_directory->kind = DirectoryFile;
      new_directory->descriptor = create_DirectoryDescriptor();
      create_DirectoryLinks(file_system, new_directory);
     }
    if (new_directory->kind!=DirectoryFile)
      return NULL;
    directory = new_directory;
    cstr = next+1;
   }
}

void setDirectoryFile_File(FileSystem *file_system, File *new_directory)
{
    new_directory->kind = DirectoryFile;
    new_directory->descriptor = create_DirectoryDescriptor();
    create_DirectoryLinks(file_system, new_directory);
}

File* registerDirectory( FileSystem* file_system, CString* path )
{
    File *new_directory;

    new_directory = registerFile(file_system, path);
    setDirectoryFile_File(file_system, new_directory);
    return new_directory;
}


void setSymLink_File( FileSystem *file_system, File *file, CString *destination)
{
    file->kind = SymbolicLinkFile;
    file->descriptor = clone(destination);
}


File *registerSymLink( FileSystem *file_system, CString *path, CString *destination)
{
    File *file = registerFile(file_system, path);
    setSymLink_File(file_system, file, destination);
    return file;
}

CString *resolveSymLink_Path(FileSystem *file_system, CString *path)
{
    File *file = getFile_FileSystem(file_system, path);
    if(NULL!=file && file->kind==SymbolicLinkFile)
    {
        CString *resolved_name= file->descriptor;
        return resolved_name;
    }
    return NULL;
}

CString *resolveAbsolutePath_Ext(CallContext context, FileSystem *fs, CString *path, Bool3 *eloop)
{
    List *links = create_List(&type_FileIdObj);
    LongT symLoopMax;
    int nresolved = 0;
    CString *resolved_path;

    *eloop = False_Bool3;
    resolved_path = resolveDirLinks_Path(path);

    //DUMP("path ==$(obj)\n", path);
    //DUMP("dir: res ==$(obj)\n", resolved_path);

    resolved_path = resolveSymLinks_Path_Ext(fs, resolved_path, eloop, links, &nresolved);

    //DUMP("sym: res ==$(obj)\n", resolved_path);

    symLoopMax = getPathSystemConfigurationValue(context, path, SUT_SC_SYMLOOP_MAX);

    if(symLoopMax==SC_VALUE_UNKNOWN)
        *eloop = or_Bool3(*eloop,Unknown_Bool3);
    else if(nresolved>symLoopMax)
        *eloop = True_Bool3;
    return resolved_path;
}

CString *resolvePath_Ext(CallContext context, FileSystem *fs, CString *path, Bool3 *eloop)
{
    CString *abspath = convertToAbsolute_Path(context, path);
    return resolveAbsolutePath_Ext(context, fs, abspath, eloop);
}

CString *resolvePathSymLink_Ext(CallContext context, FileSystem *fs, CString *path, Bool3 *eloop)
{
    CString *abspath = convertToAbsolute_Path(context, path);
    CString *dirpath = getParentDir_Path(abspath);
    CString *basename = getBaseName_Path(abspath);
    CString *resolved_dirpath = resolveAbsolutePath_Ext(context, fs, dirpath, eloop);
    return concat_Path(resolved_dirpath, basename);
}

File *getSymLink_File(CallContext context, FileSystem *fs, File *symlink, Bool3 *eloop)
{
    File *destFile;
    if(NULL==symlink || symlink->descriptor==NULL || symlink->kind!=SymbolicLinkFile)
        return NULL;
    destFile = getFile_FileSystem(fs, resolvePathSymLink_Ext(context, fs, symlink->descriptor, eloop));
    return destFile;
}

CString *resolveSymLink_Directory(FileSystem *file_system, File *directory, CString *name)
{
    File *file = getFile_Directory_Ext(file_system, directory, name);
    if(NULL!=file && file->kind==SymbolicLinkFile)
    {
        CString *resolved_name= file->descriptor;
        return resolved_name;
    }
    return NULL;
}

CString *resolveSymLinks_Path_Ext(FileSystem *file_system, CString *srcpath, Bool3 *eloop, List *resolved_links, int *nresolved)
{
    char *cstr = toCharArray_CString(srcpath);
    char *next;
    CString *result, *name, *resname;
    File *directory;
    File *linkfile;

    if(*cstr==0)
        return srcpath; // not supporting empty paths or relative paths

    if (strcmp(cstr,"/") == 0)
        return srcpath;

    directory = get_Map( file_system->files, create_FileIdObj(file_system->root) );

    result = create_CString("/");

    cstr++;

    for(;;)
    {
        next = strchr( cstr, '/' );
        name = (next==NULL) ? create_CString(cstr) : substring_CString(create_CString(cstr), 0, next-cstr);
        linkfile = getFile_Directory_Ext(file_system, directory, name);
        if(linkfile!=NULL && linkfile->kind==SymbolicLinkFile)
        {
            List *resolved_sublinks = clone(resolved_links);
            if(contains_List(resolved_links, create_FileIdObj(linkfile->fileid)))
            {
                *eloop = True_Bool3;
                return NULL;
            }
            append_List(resolved_sublinks, create_FileIdObj(linkfile->fileid));

            resname = linkfile->descriptor;

            if(!resname)
                return NULL;    // if empty link, return NULL for can't resolve

            if(isAbsolute_Path(resname))
                result = resname;
            else
                result = concat_Path(result, resname);

            result = resolveSymLinks_Path_Ext(file_system, result, eloop, resolved_sublinks, nresolved);
            if(!result)
                return NULL;
            (*nresolved)++;
        }else
            result = concat_Path(result, name);
        if(!next)
            break;
        directory = getDirectory_FileSystem(file_system, result);
        cstr = next+1;
        if(!directory)
        {
            result=concat_Path(result, create_CString(cstr));
            break;
        }
    }
    return result;
}

File *fileExist_FileSystem( FileSystem *file_system, CString* path, FileKind kind)
{
    CString *parent_path = getParentDir_Path(path);
    File *parent_directory = parent_path ? getDirectory_FileSystem(file_system, parent_path) : NULL;
    bool parent_directory_uptodate = parent_directory && isUptodate_Directory(parent_directory);

    // update knowledge about directories here only if
    // parent_directory isn't known to be synchronized
    File *file = getFile_FileSystem(file_system, path);
    if(file==NULL)
    {
        if(!parent_directory_uptodate)
            file = registerFile_Kind( file_system, path, kind );
        else
        {
            traceFormattedUserInfo("Requirements Failed: directory $(obj) "
                "found to be uptodate while adding $(obj)",
                path, parent_path);
            assertion(false, "fileExist_FileSystem: couldn't add to uptodate directory");
        }
    }else if(file->kind==UnknownFileKind)
    {
        setKind_File(file_system, file, kind);
    }else if(kind!=UnknownFileKind && file->kind!=kind)
    {
            traceFormattedUserInfo("Requirements Failed: file $(obj) "
                "expected to be directory",
                path);
            assertion(false, "fileExist_FileSystem: file exists as not directory");
    }
    return file;
}

bool isRoot_File( FileSystem *file_system, File *file )
{
    if(equals(create_FileIdObj(file->fileid), create_FileIdObj(file_system->root)))
        return true;
    return false;
}

File *getParentDirectory_UpwardLink(FileSystem *file_system, UpwardLink *uplink)
{
    return getFile_FileId_Ext(file_system, uplink->directory);
}

UpwardLink *getUpwardLinkAt_File(File *file, int index)
{
    UpwardLink *uplink;

    assertion(file!=NULL, "getUpwardLinksAt_File: file!=NULL");

    uplink = get_Set(file->parents, index);

    return uplink;
}

int getUpwardLinksCount_File(File *file)
{
    int parents_count;

    assertion(file!=NULL, "getUpwardLinksCount_File: file!=NULL");

    parents_count = size_Set(file->parents);

    return parents_count;
}

NLinkTObj *incLinksCount_File(FileSystem *file_system, File *file, NLinkT delta)
{
    if(file->nlink != NULL)
        *file->nlink += delta;
    return file->nlink;
}

// returns difference between b's links count and a's links count or NULL if unknown
NLinkTObj *diffLinksCount_File(File *a, File *b)
{
    if(NULL==a || NULL==a->nlink || NULL==b || NULL==b->nlink)
        return NULL;
    return create_NLinkTObj(*b->nlink - *a->nlink);
}

int getLinksCount_File(FileSystem *file_system, File *file)
{
    if(file->nlink != NULL)
        return *file->nlink;
    return size_Set(file->parents);
}

int getFileCount_Directory(File *file)
{
    DirectoryDescriptor *dd;
    assertion(file->kind==DirectoryFile, "getFileCount_Directory: file is not directory");
    dd = file->descriptor;

    return size_Map(dd->files);
}

bool isDirectoryUplink_Name(FileSystem *file_system, CString *filename)
{
    if(equals(filename, create_CString(".")))
        return true;
    else if(equals(filename, create_CString("..")))
        return true;
    return false;
}

Bool3 isDescendantOf_Directory(FileSystem *file_system, File *descendant, File *ancestor)
{
    int i, count;
    Bool3 res;

    assertion(file_system != NULL,
        "isDescendantOf_Directory: file_system is NULL");
    assertion(descendant != NULL,
        "isDescendantOf_Directory: descendant is NULL");
    assertion(ancestor != NULL,
        "isDescendantOf_Directory: ancestor is NULL");
    assertion(ancestor->kind == DirectoryFile,
        "isDescendantOf_Directory: ancestor is not a directory");

    res = /* TODO: uplinks are uptodated */ false ? False_Bool3 : Unknown_Bool3;

    count = getUpwardLinksCount_File(descendant);
    for(i = 0; i < count; i++)
    {
        UpwardLink *uplink = getUpwardLinkAt_File(descendant, i);
        File *parent = getParentDirectory_UpwardLink(file_system, uplink);

        if(equals_FileId(parent->fileid, ancestor->fileid))
        {
            return True_Bool3;
        }
        else
        {
            Bool3 tmp = isDescendantOf_Directory(file_system, parent, ancestor);

            if(tmp == True_Bool3)
            {
                return True_Bool3;
            }
            else if(tmp == Unknown_Bool3)
            {
                res = Unknown_Bool3;
            }
        }
    }

    return res;
}

Bool3 isDescendantOfItself_Directory(FileSystem *file_system, File *directory)
{
    return isDescendantOf_Directory(file_system, directory, directory);
}

static Bool3 processDirectory_isDescendantOf_Directory_SymLinks
(
    FileSystem *file_system,
    File *descendant,
    File *directory,
    Set *checked
);

static Bool3 processSymLink_isDescendantOf_Directory_SymLinks
(
    FileSystem *file_system,
    File *descendant,
    File *symlink,
    Set *checked
)
{
    FileIdObj *symlink_fileid_obj = create_FileIdObj(symlink->fileid);

    if(contains_Set(checked, symlink_fileid_obj))
    {
        return False_Bool3;
    }

    add_Set(checked, symlink_fileid_obj);

    if(symlink->descriptor == NULL)
    {
        return Unknown_Bool3;
    }
    else
    {
        File *file = getFile_FileSystem(file_system, symlink->descriptor);
        FileIdObj *fileid_obj = create_FileIdObj(file->fileid);

        if(equals_FileId(file->fileid, descendant->fileid))
        {
            return True_Bool3;
        }
        else if(file->kind == UnknownFileKind)
        {
            return Unknown_Bool3;
        }
        else if(file->kind == DirectoryFile)
        {
            return processDirectory_isDescendantOf_Directory_SymLinks
                (file_system, descendant, file, checked);
        }
        else if(file->kind == SymbolicLinkFile)
        {
            return processSymLink_isDescendantOf_Directory_SymLinks
                (file_system, descendant, file, checked);
        }
        else
        {
            return False_Bool3;
        }
    }

    return Unknown_Bool3;
}

static Bool3 processDirectory_isDescendantOf_Directory_SymLinks
(
    FileSystem *file_system,
    File *descendant,
    File *directory,
    Set *checked
)
{
    int i, count;
    Bool3 res;

    FileIdObj *directory_fileid_obj = create_FileIdObj(directory->fileid);

    if(contains_Set(checked, directory_fileid_obj))
    {
        return False_Bool3;
    }

    add_Set(checked, directory_fileid_obj);

    res = isUptodate_Directory(directory) ? False_Bool3 : Unknown_Bool3;

    count = getFileCount_Directory(directory);
    for(i = 0; i < count; i++)
    {
        File *file = getFileAt_Directory(file_system, directory, i);
        FileIdObj *fileid_obj = create_FileIdObj(file->fileid);

        if(equals_FileId(file->fileid, descendant->fileid))
        {
            res = True_Bool3;
        }
        else if(file->kind == UnknownFileKind)
        {
            res = Unknown_Bool3;
        }
        else if(file->kind == DirectoryFile)
        {
            res = or_Bool3(res, processDirectory_isDescendantOf_Directory_SymLinks
                (file_system, descendant, file, checked));
        }
        else if(file->kind == SymbolicLinkFile)
        {
            res = or_Bool3(res, processSymLink_isDescendantOf_Directory_SymLinks
                (file_system, descendant, file, checked));
        }

        if(res == True_Bool3)
        {
            return True_Bool3;
        }
    }

    return res;
}

Bool3 isDescendantOf_Directory_SymLinks(FileSystem *file_system, File *descendant,
                                        File *ancestor)
{
    Set *checked = create_Set(&type_FileIdObj);

    assertion(file_system != NULL,
        "isDescendantOf_Directory_SymLinks: file_system is NULL");
    assertion(descendant != NULL,
        "isDescendantOf_Directory_SymLinks: descendant is NULL");
    assertion(ancestor != NULL,
        "isDescendantOf_Directory_SymLinks: ancestor is NULL");
    assertion(ancestor->kind == DirectoryFile || ancestor->kind == SymbolicLinkFile,
        "isDescendantOf_Directory_SymLinks: ancestor is not a directory");

    if(ancestor->kind == DirectoryFile)
    {
        return processDirectory_isDescendantOf_Directory_SymLinks
            (file_system, descendant, ancestor, checked);
    }
    else
    {
        return processSymLink_isDescendantOf_Directory_SymLinks
            (file_system, descendant, ancestor, checked);
    }
}

Bool3 isDescendantOfItself_Directory_SymLinks(FileSystem *file_system, File *directory)
{
    return isDescendantOf_Directory_SymLinks(file_system, directory, directory);
}

Bool3 isEmpty_Directory(FileSystem *file_system, File *file)
{
    DirectoryDescriptor *dd;
    int count;

    if(NULL==file || file->kind==Unknown_Bool3)
        return Unknown_Bool3;

    if(file->kind!=DirectoryFile)
        return False_Bool3;

    dd = file->descriptor;

    count = size_Map(dd->files);
    if(count>2)
        return False_Bool3;
    while(count-->0)
    {
        if(!isDirectoryUplink_Name(file_system, getFileNameAt_Directory(file_system, file, count)))
            return False_Bool3;
    }

    return dd->uptodate?True_Bool3:Unknown_Bool3;
}

File* getFileAt_Directory(FileSystem *file_system, File *file, int index)
{
   DirectoryDescriptor *dd;
   FileIdObj *fileid;

   assertion(file->kind==DirectoryFile, "getFileAt_Directory: file is not directory");
   dd = file->descriptor;

   fileid = get_Map(dd->files, key_Map(dd->files, index));

   return getFile_FileId_Ext(file_system, *fileid);
}

CString* getFileNameAt_Directory(FileSystem *file_system, File *file, int index)
{
   DirectoryDescriptor *dd;
   CString *name;

   assertion(file->kind==DirectoryFile, "getFileNameAt_Directory: file is not directory");
   dd = file->descriptor;

   name = key_Map(dd->files, index);

   return name;
}

bool isModified_Directory(FileSystem *file_system, File *directory)
{
   DirectoryDescriptor *dd;

   assertion(directory->kind==DirectoryFile, "isModified_Directory: file is not directory");
   dd = directory->descriptor;

   return dd->modified;
}

void setModified_Directory(FileSystem *file_system, File *directory, bool modified)
{
   DirectoryDescriptor *dd;

   assertion(directory->kind==DirectoryFile, "setModified_Directory: file is not directory");
   dd = directory->descriptor;

   dd->modified = modified;
}


/*int getSymLinkCount_Directory(File *file)
{
   DirectoryDescriptor *dd;
   FileIdObj *fileid;

   assertion(isDirectory_File(file), "getSymLinkCount_Directory: file is not directory");
   dd = file->descriptor;

   return size_Map(dd->symlinks);
}

CString *getSymLinkAt_Directory(File *file, int index)
{
   DirectoryDescriptor *dd;
   FileIdObj *fileid;

   assertion(isDirectory_File(file), "getSymLinkAt_Directory: file is not directory");
   dd = file->descriptor;

   return key_Map(dd->symlinks, index);
}
*/
CString *getName_File( FileSystem *file_system, File *file )
{
    CString *name;
    int uplinks_count;
    UpwardLink *uplink;

    assertion(file!=NULL,"getName_File: file!=NULL");

    if(isRoot_File(file_system, file))
        return create_CString("/");

    uplinks_count = size_Set(file->parents);

    if(uplinks_count==0)
        return NULL;

    uplink = get_Set(file->parents,0);
    return uplink->filename;
}

// returns path for file using first upward link always
CString *getPath_File( FileSystem *fs, File *file )
{
    CString *path = NULL;
    int uplinks_count;
    UpwardLink *uplink;

    assertion(file!=NULL,"getPath_File: file!=NULL");

    if(isRoot_File(fs, file))
        return create_CString("/");


    while((uplinks_count=size_Set(file->parents))>0)
    {
        uplink = get_Set(file->parents, 0);
        if(!path)
            path = clone(uplink->filename);
        else
        {
            path = concat_CString(clone(uplink->filename), path);
        }
        if(charAt_CString(path,0)!='/')
            path = concat_CString(create_CString("/"), path);
        file = getFile_FileId_Ext(fs, uplink->directory);
    }

    if(isRoot_File(fs, file))
        path = concat_CString(create_CString(""), path);

    return path;
}

File* getFile_FileId( FileId fileid )
{
SystemState* systemState = getSystemState(fileid.system);

  if (systemState == NULL)
    return NULL;
  return get_Map( systemState->file_system->files, create_FileIdObj(fileid) );
}

// _Ext version uses explicit file_system object (needed for updating FileSystem clones)
File* getFile_FileId_Ext( FileSystem *file_system, FileId fileid )
{
  if (file_system == NULL)
    return NULL;
  return get_Map( file_system->files, create_FileIdObj(fileid) );
}

File* getFile_Directory( File* directory, CString* filename)
{
DirectoryDescriptor* dd;
FileIdObj* fileid;

  if (directory->kind!=DirectoryFile)
  {
    return NULL;
  }
  dd = directory->descriptor;
  fileid = get_Map( dd->files, filename );
  if (fileid == NULL)
  {
    return NULL;
  }
  return getFile_FileId(*fileid);
}

File* getFile_FileSystem( FileSystem* file_system, CString* path )
{
    FileSearchResult *result = getFile_FileSystem_Ext(file_system, path);
    File *file;
    file = result->file;
    return file;
}


// unlike getFile_Directory, this function don't use getFile_FileId
// to search through given file system (that could be a clone)
File* getFile_Directory_Ext( FileSystem *file_system, File* directory, CString* filename)
{
DirectoryDescriptor* dd;
FileIdObj* fileid;

  if (directory->kind!=DirectoryFile)
  {
    return NULL;
  }
  dd = directory->descriptor;
  fileid = get_Map( dd->files, filename );
  if (fileid == NULL)
  {
    return NULL;
  }
  return get_Map(file_system->files, fileid);
}

FileSearchResult* getFile_FileSystem_Ext( FileSystem* file_system, CString* path)
{
CharT *cstr,*next;
File* directory;
FileSearchResult *result;

  result = create_FileSearchResult();

  if(!path)
      return result;

  cstr = toCharArray_CString(path);

  //assertion( cstr[0] == '/', "getFile_FileSystem: full file name expected %s", cstr );

  if(cstr[0] != '/')
      return result;

  if (strcmp(cstr,"/") == 0)
   {
     result->last = get_Map( file_system->files, create_FileIdObj(file_system->root) );
     result->file = result->last;
     return result;
   }
  directory = get_Map( file_system->files, create_FileIdObj(file_system->root) );
  result->last = directory;

  cstr = cstr + 1;

  result->rest_path = create_CString(cstr);

  for(;;)
   {
    next = strchr( cstr, '/' );
    if (next == NULL)
     {
        result->file = getFile_Directory_Ext( file_system, directory, create_CString(cstr));
        result->rest_path = create_CString(cstr);
        if(NULL!=result->file)
        {
            result->last = result->file;
        }
        return result;
     }
    directory = getFile_Directory_Ext( file_system, directory, substring_CString( create_CString(cstr), 0, next - cstr ) );
    if(NULL!=directory)
            result->last = directory;
    if (directory == NULL || directory->kind!=DirectoryFile)
    {
       result->rest_path = create_CString(cstr);
       return result;
    }
    cstr = next+1;
   }
}


static void unregisterFile_Directory( FileSystem *file_system, File *file, UpwardLink *uplink)
{
    DirectoryDescriptor *dd;
    CString *name;
    File *directory = getFile_FileId_Ext(file_system, uplink->directory);

    assertion(directory->kind==DirectoryFile, "unregisterFile_Directory: parent shall be directory" );

    dd = directory->descriptor;

    remove_Map(dd->files, uplink->filename);

    remove_Set(file->parents, uplink);

    dd->modified = true;

}

void unregisterFile( FileSystem *file_system, File *file)
{
    DUMP("unregisterFile $(obj)\n", getPath_File(file_system, file));
    if(file->kind==DirectoryFile)
    {
        unregisterDirLink_Directory(file, create_CString("."));
        unregisterDirLink_Directory(file, create_CString(".."));

        while(getFileCount_Directory(file) > 0)
        {
            //int fcount_pre = getFileCount_Directory(file), fcount_post;
            File *child_file = getFileAt_Directory(file_system, file, 0);
            CString *child_name = getFileNameAt_Directory(file_system, file, 0);
            //dump("d $(obj)\n", child_name);
            unregisterFile(file_system, child_file);
            //fcount_post = getFileCount_Directory(file);
            //dump("~ $(obj)\n", create_Integer(fcount_post-fcount_pre));

        }
    }

    // unregister self from parent directories
    while(size_Set(file->parents) > 0)
    {
        UpwardLink *uplink = get_Set(file->parents,0);
        unregisterFile_Directory(file_system, file, uplink);
    }

}

bool doesFileExist_Directory( File* directory, CString* filename )
{
    return !equals(getFile_Directory(directory, filename), NULL);
}

Bool3 doesFileExist_FileSystem( FileSystem* file_system, CString* path )
{
    File *file = getFile_FileSystem(file_system, path);

    if(NULL!=file)
        return True_Bool3;
    else if(!isAbsolute_Path(path))
        return False_Bool3;
    else
    {
        CString *dir_path = getParentDir_Path(path);
        File *dir;
        if(dir_path)
        {
            dir=getFile_FileSystem(file_system, dir_path);

            /*TODO: resolveSymlink to dir... Otherwise we get error cause
            CString* is assigned to DirectoryDescriptor*
            if(NULL!=dir)
            {
                DirectoryDescriptor *dd = dir->descriptor;
                if(dd!=NULL && dd->uptodate)
                    return False_Bool3;
            }
            */
        }
        return Unknown_Bool3;
    }
}

bool isAbsolute_Path(CString *path)
{
    return path!=NULL && length_CString(path)>0 && charAt_CString(path,0)=='/';
}

bool isRelative_Path(CString *path)
{
    return path!=NULL && length_CString(path)>0 && charAt_CString(path,0)!='/';
}

List *split_Path(CString *path)
{
    CString *temp = clone(path);
    char *str = toCharArray_CString(temp);
    List *retval = create_List(&type_CString);

    char *slash;

    while(*str=='/')
    {
        str++;
    }
    slash = str;
    while(*str && (slash=strchr(slash+1,'/'))!=NULL)
    {
        *slash = 0;
        append_List(retval, create_CString(str));
        str = slash+1;
        while(*str=='/')
        {
            str++;
        }
    }
    if(*str)
        append_List(retval, create_CString(str));

    return retval;
}

CString *resolveDirLinks_Path2(CString *path)
{
    List *comps = split_Path(path);
    int index = size_List(comps)-1;
    CString *result = create_CString("");

    while(index>=0)
    {
        CString *name = get_List(comps, index);

        if(equals(name,create_CString(".")))    // skip "."
            index--;
        else if(equals(name, create_CString(".."))) // resolve ".."
            index-=2;
        else if(length_CString(name)==0)    // convert "//" to "/"
            index--;
        else
        {
            result = concat_Path(name, result);
            index--;
        }
    }
    if(length_CString(path)>0 && charAt_CString(path,0)=='/')
        result = concat_CString(create_CString("/"), result);
    return result;
}

//This is new version. Old version is named know resolveDirLinks_Path2.
//There is a bug in old version. Maybe there is some in new version. Who knows?
CString *resolveDirLinks_Path(CString *path)
{
    List *comps = split_Path(path);
    int index = 0;
    CString *result = create_CString("");

    while(index < size_List(comps))
    {
        CString *name = get_List(comps, index);

        if(equals(name,create_CString(".")))    // skip "."
            remove_List(comps, index);
        else if(equals(name, create_CString(".."))) // resolve ".."
        {
            remove_List(comps, index);

            if(index > 0)
                remove_List(comps, --index);
        }
        else if(length_CString(name)==0)    // convert "//" to "/"
            remove_List(comps, index);
        else
            index++;
    }

    index = -1;

    while(++index < size_List(comps))
        result = concat_Path(result, (CString*)get_List(comps, index));


    if(length_CString(path)>0 && charAt_CString(path,0)=='/')
        result = concat_CString(create_CString("/"), result);

    return result;
}


CString *convertToAbsolute_Path(CallContext context, CString *path)
{
    ProcessState *pstate = getProcessState_CallContext(context);
    FileSystem *fs = getFileSystem(context);
    if(isRelative_Path(path))
    {
        if(!equals(create_FileIdObj(WrongFileId), create_FileIdObj(pstate->meta.workdir)))
        {
            File *file = getFile_FileId_Ext(fs, pstate->meta.workdir);
            CString *basepath = getPath_File(fs, file);
            path = concat_Path(basepath, path);
        }else
            assertion(false, "convertToAbsolute_Path: no working directory found in model");
    }
    return path;
}

bool isEmpty_Path(CString *path)
{
    return length_CString(path)==0;
}

bool isRoot_Path(CString *path)
{
    return length_CString(path)==1 && charAt_CString(path,0)=='/';
}

File *getDirectory_FileSystem(FileSystem *file_system, CString *path)
{
    File *directory = getFile_FileSystem(file_system, path);
    if(!directory)
        return NULL;
    assertion(directory->kind==DirectoryFile, "getDirectory_FileSystem: is not directory %s", toCharArray_CString(path));
    return directory;
}

bool isUptodate_Directory(File *file)
{
    DirectoryDescriptor *dd;

    assertion(file->kind==DirectoryFile, "isUptodate_Directory: is not directory");
    dd=file->descriptor;
    assertion(dd!=NULL, "isUptodate_Directory: descriptor==NULL");
    return dd->uptodate;
}

CString *getParentDir_Path(CString *path)
{
    int i;
    int l;

    if(NULL==path)
        return NULL;

    l = length_CString(path);
    for( i = l-1; i>=0; i-- )
    {
        if(charAt_CString(path, i)=='/')
            return i>0 ?
                substring_CString(path,0,i) // "/x/y" case
                : (l>1?create_CString("/")  // "/x" case
                    :NULL);                 // "/" case
    }
    return NULL;
}

CString *getBaseName_Path(CString *path)
{
    int i;
    int l = length_CString(path);
    for( i = l-1; i>=0; i-- )
    {
        if(charAt_CString(path, i)=='/')
            return substring_CString(path,i+1,l);
    }
    return path;
}

CString *concat_Path(CString *parent, CString *child)
{
    CString *result = parent;

    if(     length_CString(parent) > 0
        &&  charAt_CString(parent, length_CString(parent)-1) != '/'
        && length_CString(child)>0)
        result = concat_CString(parent, create_CString("/"));

    result = concat_CString(result, child);

    return result;
}

/********************************************************************/
/** FileSearchResult                                               **/
/********************************************************************/
specification typedef struct FileSearchResult FileSearchResult = {};
FileSearchResult *create_FileSearchResult()
{
    return create(&type_FileSearchResult, NULL, NULL, NULL);
}


/********************************************************************/
/** uses 'system' specification function to execute rm -rf <target> */
/** and updates model                                              **/
/********************************************************************/
void destroyFolder(CallContext context, CString *path);

void recursive_remove( CallContext context, CString *directory)
{
    ErrorCode *error = create_ErrorCode(0);
    File *dir;
    FileSystem *file_system = getFileSystem(context);
    File *parent_dir;
    bool mode;

    mode = setDeferredReactionsMode(false);

    destroyFolder(context, directory);

    dir = getFile_FileSystem(file_system, directory);

    if(NULL!=dir)
    {
        parent_dir = getFile_FileSystem(file_system, getParentDir_Path(directory));

        if(NULL!=parent_dir)
        {
            parent_dir->ctime_updated = false;
            parent_dir->mtime_updated = false;
            parent_dir->atime_updated = false;
        }

        unregisterFile(file_system, dir);
    }



    setDeferredReactionsMode(mode);
}

/********************************************************************/
/**                                                                **/
/********************************************************************/

FilePermission* getProcessAccessOnFile(CallContext context, File *file)
{
    FilePermission* perms = NULL;
    ProcessId processId = getProcessId_CallContext(context);
    UidTObj *puid = getEffectiveUserId_ProcessId(processId);
    GidTObj *pgid = getEffectiveGroupId_ProcessId(processId);

    if(NULL==file->permissions)
        return perms;       // no knowledge about perms



    if(file->permissions->other!=NULL)
    {
        perms=clone(file->permissions->other);
    }


    if(NULL!=pgid && equals(file->gid,pgid) ) // if process is in group
    {
        if(file->permissions->group!=NULL)
        {
            perms=clone(file->permissions->group);
        }

    }

    if(NULL!=puid && equals(file->uid,puid))   // if owner process
    {
        if(file->permissions->owner!=NULL)
        {
            perms=clone(file->permissions->owner);
        }

    }
    return perms;
}

Bool3 isNoPermOnPath(CallContext context, FileSystem *fs, CString *path, bool read, bool write, bool execute)
{
    Bool3 res = False_Bool3;
    File *dir;
    FilePermission* perms;
    ProcessState* ps=getProcessState_CallContext(context);

    dir = getFile_FileSystem(fs, path);

    if(ps->meta.effective_userid==(UidT)0)//*****Improve?*****
    {
        return Unknown_Bool3;
    }

    if(NULL!=dir)
    {
        perms = getProcessAccessOnFile(context, dir);
        if(execute)
        {
            if(perms!=NULL && perms->execute==False_Bool3)
                res = True_Bool3;
            else if(perms==NULL && res==False_Bool3)
               res = Unknown_Bool3; // unknown perms of path component
        }
        if(read)
        {
            if(perms!=NULL && perms->read==False_Bool3)
                res = True_Bool3;
            else if(perms==NULL &&  res==False_Bool3)
               res = Unknown_Bool3; // unknown perms of path component
        }
        if(write)
        {
            if(perms!=NULL && perms->write==False_Bool3)
                res = True_Bool3;
            else if(perms!=NULL &&  res==False_Bool3)
               res = Unknown_Bool3; // unknown perms of path component
        }
    }else
        res = Unknown_Bool3;    // some path component is not in model
    return res;
}

Bool3 isNoPermOnPathComponents(CallContext context, FileSystem *fs, CString *path, bool read, bool write, bool execute)
{
    Bool3 res = False_Bool3;
    File *dir;
    FilePermission* perms;
    while(!equals(path, NULL))   // while not root path
    {
        res = or_Bool3( res, isNoPermOnPath(context, fs, path, read, write, execute));
        path = getParentDir_Path(path);
    }
    return res;
}

/* [EMLINK]
 *  The link count of the parent directory would exceed {LINK_MAX}.
 */
Bool3 isEMLINK(CallContext context, FileSystem *fs, CString *path)
{
    int link_count;
    LongT link_max;

    File *parent_dir;

    path = getParentDir_Path(path);

    if(NULL==path)  // no parent path
        return False_Bool3;

    parent_dir = getFile_FileSystem(fs, path);

    if(NULL==parent_dir)
        return Unknown_Bool3;

    link_max = getPathSystemConfigurationValue(context,
                    path, SUT_PC_LINK_MAX);
    if(link_max==SC_VALUE_UNKNOWN)
        return Unknown_Bool3;

    link_count = getFileCount_Directory(parent_dir);

    return (link_count>link_max)?True_Bool3:False_Bool3;
}

Bool3 isENAMETOOLONG(CallContext context, CString *path)
{
    LongT path_max;
    LongT name_max;
    int i;
    List *path_components;

    path_max = getPathSystemConfigurationValue(context, path, SUT_PC_PATH_MAX);

    if(SC_VALUE_UNKNOWN==path_max)
        return Unknown_Bool3;

    if(length_CString(path)>path_max)
        return True_Bool3;

    name_max = getPathSystemConfigurationValue(context, path, SUT_PC_NAME_MAX);

    if(SC_VALUE_UNKNOWN==name_max)
        return Unknown_Bool3;

    path_components = split_Path(path);
    for(i=0; i<size_List(path_components); i++)
    {
        CString *path_comp = get_List(path_components, i);
        if(length_CString(path_comp) > name_max)
            return True_Bool3;
    }
    return False_Bool3;
}