/*++ Copyright (c) 2000 Microsoft Corporation Module Name: fsutil.c Abstract: Implements interface to underlying filesystem Author: Ahmed Mohamed (ahmedm) 1-Feb-2000 Revision History: --*/ #include #include #include #include #include #include #include #include #include #include #include "fs.h" #include "fsp.h" #include "fsutil.h" #include #define XFS_ENUM_FIRST 0x1 #define XFS_ENUM_LAST 0x2 #define XFS_ENUM_DIR 0x4 typedef NTSTATUS (*PXFS_ENUM_CALLBACK)(PVOID,HANDLE,PFILE_DIRECTORY_INFORMATION); NTSTATUS xFsCreate(HANDLE *fd, HANDLE root, LPWSTR buf, int n, UINT32 flag, UINT32 attrib, UINT32 share, UINT32 *disp, UINT32 access, PVOID eabuf, int easz) { OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; NTSTATUS status; IO_STATUS_BLOCK iostatus; n = n * sizeof(WCHAR); cwspath.Buffer = buf; cwspath.Length = (USHORT) n; cwspath.MaximumLength = (USHORT) n; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); // if write access is enabled, we turn on write-through bit if (access & FILE_WRITE_DATA) { flag |= FILE_WRITE_THROUGH; } *fd = INVALID_HANDLE_VALUE; status = NtCreateFile(fd, SYNCHRONIZE | access, &objattrs, &iostatus, 0, attrib, share, *disp, FILE_SYNCHRONOUS_IO_ALERT | flag, eabuf, easz); *disp = (UINT32) iostatus.Information; if (NT_SUCCESS(status)) { status = STATUS_SUCCESS; } else { *fd = INVALID_HANDLE_VALUE; } return status; } NTSTATUS xFsOpen(HANDLE *fd, HANDLE root, LPWSTR buf, int n, UINT32 access, UINT32 share, UINT32 flags) { OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; IO_STATUS_BLOCK iostatus; n = n * sizeof(WCHAR); cwspath.Buffer = buf; cwspath.Length = (USHORT) n; cwspath.MaximumLength = (USHORT) n; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); *fd = INVALID_HANDLE_VALUE; return NtOpenFile(fd, SYNCHRONIZE | access, &objattrs, &iostatus, share, flags | FILE_SYNCHRONOUS_IO_ALERT); } NTSTATUS xFsOpenEx(HANDLE *fd, HANDLE root, LPWSTR buf, int n, UINT32 access, UINT32 share, UINT32 flags) { OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; IO_STATUS_BLOCK iostatus; n = n * sizeof(WCHAR); cwspath.Buffer = buf; cwspath.Length = (USHORT) n; cwspath.MaximumLength = (USHORT) n; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); *fd = INVALID_HANDLE_VALUE; return NtOpenFile(fd, access, &objattrs, &iostatus, share, flags); } NTSTATUS xFsDelete(HANDLE root, LPWSTR buf, int n) { OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; n = n * sizeof(WCHAR); cwspath.Buffer = buf; cwspath.Length = (USHORT) n; cwspath.MaximumLength = (USHORT) n; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); return NtDeleteFile(&objattrs); } NTSTATUS xFsQueryObjectId(HANDLE fd, PVOID id) { NTSTATUS status; IO_STATUS_BLOCK iostatus; fs_ea_t x; fs_ea_name_t name; FsInitEa(&x); FsInitEaName(&name); status = NtQueryEaFile(fd, &iostatus, (PVOID) &x, sizeof(x), TRUE, (PVOID) &name, sizeof(name), NULL, TRUE); if (status == STATUS_SUCCESS) { fs_id_t *fid; if (iostatus.Information > sizeof(x.hdr)) { FsInitEaFid(&x, fid); memcpy(id, fid, sizeof(fs_id_t)); } else { memset(id, 0, sizeof(fs_id_t)); } } else { FsLog(("QueryEa failed %x\n", status)); } return status; } NTSTATUS xFsRename(HANDLE fh, HANDLE root, WCHAR *dname, int n) { NTSTATUS status; IO_STATUS_BLOCK iostatus; struct { FILE_RENAME_INFORMATION x; WCHAR buf[MAXPATH]; }info; info.x.ReplaceIfExists = TRUE; info.x.RootDirectory = root; ASSERT(n == (int)wcslen(dname)); // convert to unicode StringCchCopyW(info.x.FileName, MAXPATH, dname); info.x.FileNameLength = n * sizeof(WCHAR); status = NtSetInformationFile(fh, &iostatus, (PVOID) &info, sizeof(info), FileRenameInformation); return status; } NTSTATUS xFsSetAttr(HANDLE fd, FILE_BASIC_INFORMATION *attr) { IO_STATUS_BLOCK iostatus; return NtSetInformationFile(fd, &iostatus, (PVOID) attr, sizeof(*attr), FileBasicInformation); } NTSTATUS xFsQueryAttr(HANDLE fd, FILE_NETWORK_OPEN_INFORMATION *attr) { IO_STATUS_BLOCK iostatus; return NtQueryInformationFile(fd, &iostatus, (PVOID)attr, sizeof(*attr), FileNetworkOpenInformation); } NTSTATUS xFsQueryAttrName(HANDLE root, LPWSTR buf, int n, FILE_NETWORK_OPEN_INFORMATION *attr) { NTSTATUS err; OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; n = n * sizeof(WCHAR); cwspath.Buffer = buf; cwspath.Length = (USHORT) n; cwspath.MaximumLength = (USHORT) n; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); err = NtQueryFullAttributesFile(&objattrs, attr); return err; } NTSTATUS xFsReadDir(HANDLE fd, PVOID buf, int *rlen, BOOLEAN flag) { NTSTATUS err; IO_STATUS_BLOCK iostatus; err = NtQueryDirectoryFile(fd, NULL, NULL, NULL, &iostatus, (LPVOID) buf, *rlen >> 1, FileDirectoryInformation, FALSE, NULL, flag); *rlen = (int) iostatus.Information; return err; } LPWSTR xFsBuildRelativePath(VolInfo_t *vol, int nid, LPWSTR path) { LPWSTR share, s=NULL; // Since the shares were opened using ipaddress, skip over the node name part. // share = wcschr(vol->DiskList[nid], L'\\'); // share = wcschr(vol->DiskList[nid], L'\\'); // FsLog(("xFsBuildRelativePath(%ws) root:%ws\n", path, vol->Root)); share = vol->Root; if (share != NULL) { s = wcsstr(path, share); if (s != NULL) { s += (wcslen(share)+1); s = wcsstr(s, share); if (s != NULL) { s += (wcslen(share) + 1); } } } // FsLog(("xFsBuildRelativePath() returns %ws\n", s)); return s; } NTSTATUS _FsGetHandleById(HANDLE root, fs_id_t *id, UINT32 access, HANDLE *fhdl) { NTSTATUS err, status; int sz; BOOLEAN flag = TRUE; IO_STATUS_BLOCK ios; PVOID buf; DWORD bufsize; bufsize = PAGESIZE; buf = (PVOID) VirtualAlloc(NULL, bufsize, MEM_COMMIT, PAGE_READWRITE); if (buf == NULL) { return STATUS_NO_MEMORY; } status = STATUS_OBJECT_PATH_NOT_FOUND; while (TRUE) { PFILE_DIRECTORY_INFORMATION p; err = NtQueryDirectoryFile(root, NULL, NULL, NULL, &ios, (LPVOID) buf, bufsize, FileDirectoryInformation, FALSE, NULL, flag); sz = (int) ios.Information; if (err != STATUS_SUCCESS) { // FsLogError(("ReadDir failed %x flags %d\n", err, flag)); break; } flag = FALSE; p = (PFILE_DIRECTORY_INFORMATION) buf; while (TRUE) { // open each entry and get its object id HANDLE fd; OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; cwspath.Buffer = p->FileName; cwspath.Length = (USHORT) p->FileNameLength; cwspath.MaximumLength = (USHORT) p->FileNameLength; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); // todo: what if the file is nonsharable @ this time? err = NtOpenFile(&fd, SYNCHRONIZE | ((p->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? (FILE_GENERIC_READ | FILE_GENERIC_EXECUTE) : (access)), &objattrs, &ios, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_ALERT); if (err == STATUS_SUCCESS) { fs_id_t fid; err = xFsQueryObjectId(fd, (PVOID)fid); if (err == STATUS_SUCCESS) { #ifdef DEBUG wprintf(L"Compare file %wZ, %I64x:%I64x with %I64x:%I64x\n", &cwspath, fid[0], fid[1], (*id)[0], (*id)[1]); #endif if (fid[0] == (*id)[0] && fid[1] == (*id)[1]) { #ifdef DEBUG wprintf(L"Found file %wZ, %I64x:%I64x\n", &cwspath, fid[0], fid[1]); #endif status = STATUS_SUCCESS; #if 0 if (access != FILE_GENERIC_READ) { HANDLE nfd; xFsClose(fd); err = NtOpenFile(&nfd, SYNCHRONIZE | access, &objattrs, &ios, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (err == STATUS_SUCCESS) { fd = nfd; } else { fd = 0; } } #endif *fhdl = fd; } else if (p->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { status = _FsGetHandleById(fd, id, access, fhdl); xFsClose(fd); fd = 0; } } else { FsLog(("_GetHandleById: query '%wZ' err 0x%x\n", &cwspath, err)); } if (status == STATUS_SUCCESS) { goto done; } if (fd) xFsClose(fd); } else { if (err == STATUS_SHARING_VIOLATION) { status = err; FsLog(("_GetHandleById: open '%wZ' err 0x%x\n", &cwspath, err)); break; } } if (p->NextEntryOffset == 0) break; p = (PFILE_DIRECTORY_INFORMATION) (((PBYTE)p) + p->NextEntryOffset); } } done: VirtualFree(buf, 0, MEM_RELEASE); return status; } NTSTATUS xFsGetHandleById(HANDLE root, fs_id_t *id, UINT32 access, HANDLE *fhdl) { // open each entry and get its object id HANDLE fd; NTSTATUS err; err = NtDuplicateObject(NtCurrentProcess(), root, NtCurrentProcess(), &fd, (ACCESS_MASK)0, FALSE, DUPLICATE_SAME_ACCESS); if (err != STATUS_SUCCESS) { FsLogError(("Unable to duplicate root handle %x\n", err)); return err; } err = _FsGetHandleById(fd, id, access, fhdl); xFsClose(fd); return err; } DWORD xFsGetHandlePath(HANDLE fd, WCHAR *path, int *pathlen) { NTSTATUS status; IO_STATUS_BLOCK iostatus; int orig_len=*pathlen; struct { FILE_NAME_INFORMATION x; WCHAR buf[MAXPATH]; }info; *path = L'\0'; *pathlen = 0; status = NtQueryInformationFile(fd, &iostatus, (LPVOID) &info, sizeof(info), FileNameInformation); if (status == STATUS_SUCCESS) { int k = info.x.FileNameLength / sizeof(WCHAR); info.x.FileName[k] = L'\0'; StringCbCopyW(path, orig_len, info.x.FileName); *pathlen = k; } return status; } NTSTATUS xFsGetPathById(HANDLE vfd, fs_id_t *id, WCHAR *name, int *name_len) { NTSTATUS err; HANDLE fd; err = xFsGetHandleById(vfd, id, FILE_READ_EA, &fd); if (err == STATUS_SUCCESS) { err = xFsGetHandlePath(fd, name, name_len); xFsClose(fd); } return err; } NTSTATUS _xFsEnumTree(HANDLE hdl, int mode, PXFS_ENUM_CALLBACK callback, PVOID callback_arg) { NTSTATUS err = STATUS_SUCCESS; IO_STATUS_BLOCK ios; BOOLEAN flag; PVOID buf; DWORD bufsize; bufsize = PAGESIZE; buf = (PVOID) VirtualAlloc(NULL, bufsize, MEM_COMMIT, PAGE_READWRITE); if (buf == NULL) { return STATUS_NO_MEMORY; } flag = TRUE; while (err == STATUS_SUCCESS) { PFILE_DIRECTORY_INFORMATION p; p = (PFILE_DIRECTORY_INFORMATION) buf; err = NtQueryDirectoryFile(hdl, NULL, NULL, NULL, &ios, (LPVOID) buf, bufsize, FileDirectoryInformation, FALSE, NULL, flag); if (err != STATUS_SUCCESS) { break; } flag = FALSE; while (err == STATUS_SUCCESS) { BOOLEAN skip = FALSE; if (p->FileNameLength == 2 && p->FileName[0] == L'.' || (p->FileNameLength == 4 && p->FileName[0] == L'.' && p->FileName[1] == L'.')) skip = TRUE; // skip . and .. if (skip == FALSE) { // traverse before if (mode & XFS_ENUM_FIRST) { err = callback(callback_arg, hdl, p); } if ((mode & XFS_ENUM_DIR) && (p->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { HANDLE fd; OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; cwspath.Buffer = p->FileName; cwspath.Length = (USHORT) p->FileNameLength; cwspath.MaximumLength = (USHORT) p->FileNameLength; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, hdl, NULL); // todo: what if the dir is nonsharable @ this time? err = NtOpenFile(&fd, SYNCHRONIZE | FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, &objattrs, &ios, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (err == STATUS_SUCCESS) { err = _xFsEnumTree(fd, mode, callback, callback_arg); NtClose(fd); } else { FsLog(("Open failed on traverse dir %S %x\n", p->FileName, err)); } } // traverse after if (mode & XFS_ENUM_LAST) { err = callback(callback_arg, hdl, p); } } if (p->NextEntryOffset == 0) break; p = (PFILE_DIRECTORY_INFORMATION) (((PBYTE)p) + p->NextEntryOffset); } } VirtualFree(buf, 0, MEM_RELEASE); if (err == STATUS_NO_MORE_FILES) err = STATUS_SUCCESS; return err; } NTSTATUS xFsRemove(PVOID arg, HANDLE root, PFILE_DIRECTORY_INFORMATION item) { NTSTATUS err; OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; cwspath.Buffer = item->FileName; cwspath.Length = (USHORT) item->FileNameLength; cwspath.MaximumLength = (USHORT) item->FileNameLength; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); // if the file is marked read-only, we have to clear it first before we delete if (item->FileAttributes & FILE_ATTRIBUTE_READONLY) { // clear this bit in order to delete HANDLE fd; IO_STATUS_BLOCK iostatus; err = NtOpenFile(&fd, SYNCHRONIZE | (STANDARD_RIGHTS_WRITE | FILE_WRITE_ATTRIBUTES), &objattrs, &iostatus, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (err == STATUS_SUCCESS) { FILE_BASIC_INFORMATION attr; memset((PVOID) &attr, 0, sizeof(attr)); attr.FileAttributes = item->FileAttributes & ~FILE_ATTRIBUTE_READONLY; err = NtSetInformationFile(fd, &iostatus, (PVOID) &attr, sizeof(attr), FileBasicInformation); xFsClose(fd); } } err = NtDeleteFile(&objattrs); FsLog(("xFsRemove: '%wZ' err 0x%x\n", &cwspath, err)); // The code below is an infinite loop. // while (err == STATUS_SHARING_VIOLATION) { // Sleep(5*10000); // } return err; } NTSTATUS xFsCopy(PVOID arg, HANDLE root, PFILE_DIRECTORY_INFORMATION item) { WCHAR *name = item->FileName; int name_len = item->FileNameLength / sizeof(WCHAR); NTSTATUS err; err = xFsDupFile(root, (HANDLE) arg, name, name_len, TRUE); return err; } // copy all files from mvfd to vfd. NTSTATUS xFsCopyTree(HANDLE mvfd, HANDLE vfd) { NTSTATUS err; // first, remove all files in vfd FsLog(("CopyTree: remove first\n")); err = _xFsEnumTree(vfd, XFS_ENUM_LAST|XFS_ENUM_DIR, xFsRemove, NULL); // copy files if (err == STATUS_SUCCESS) { FsLog(("CopyTree: copy second\n")); err = _xFsEnumTree(mvfd, XFS_ENUM_FIRST, xFsCopy, (PVOID) vfd); } FsLog(("CopyTree: exit %x\n", err)); return err; } // delete all files NTSTATUS xFsDeleteTree(HANDLE vfd) { // remove all files in vfd return _xFsEnumTree(vfd, XFS_ENUM_LAST|XFS_ENUM_DIR, xFsRemove, NULL); } NTSTATUS xFsTouch(PVOID arg, HANDLE root, PFILE_DIRECTORY_INFORMATION item) { NTSTATUS err=STATUS_SUCCESS; OBJECT_ATTRIBUTES objattrs; UNICODE_STRING cwspath; HANDLE fd; IO_STATUS_BLOCK iostatus; DWORD cnt = 0; cwspath.Buffer = item->FileName; cwspath.Length = (USHORT) item->FileNameLength; cwspath.MaximumLength = (USHORT) item->FileNameLength; InitializeObjectAttributes(&objattrs, &cwspath, OBJ_CASE_INSENSITIVE, root, NULL); do { err = NtOpenFile(&fd, SYNCHRONIZE | FILE_GENERIC_READ | FILE_GENERIC_WRITE, &objattrs, &iostatus, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT); if (err == STATUS_SUCCESS) { NtClose(fd); } else { FsLogError(("xFsTouch: open '%wZ' failed 0x%x\n", &cwspath, err)); if (err == STATUS_SHARING_VIOLATION || err == STATUS_DELETE_PENDING) { Sleep(5*1000); cnt++; } } } while ((err == STATUS_SHARING_VIOLATION || err == STATUS_DELETE_PENDING) && cnt < 1000); return err; } // touch each file NTSTATUS xFsTouchTree(HANDLE mvfd) { return _xFsEnumTree(mvfd, XFS_ENUM_LAST | XFS_ENUM_DIR, xFsTouch, NULL); } NTSTATUS xFsDupFile(HANDLE mvfd, HANDLE vfd, WCHAR *name, int name_len, BOOLEAN flag) { NTSTATUS err; HANDLE mfd, fd; IO_STATUS_BLOCK ios; fs_id_t *fs_id; fs_ea_t xattr; FILE_NETWORK_OPEN_INFORMATION attr; FILE_BASIC_INFORMATION new_attr; char buf[PAGESIZE]; // Create file on vfd with same name and attrib and extended attributes (object id) // If we created a directory, we are done. // Otherwise, copy all data from source file to new file // Open master file err = xFsOpenRD(&mfd, mvfd, name, name_len); if (err != STATUS_SUCCESS) { // We couldn't open source file. If file is locked we have to use the handle we // already have. todo: FsLog(("FsDupFile: unable to open source '%.*ls' err %x\n", name_len/sizeof(WCHAR), name, err)); return err; } // Query name on mvfd and obtain all attributes. err = xFsQueryAttr(mfd, &attr); if (err != STATUS_SUCCESS) { FsLog(("FsDupFile: unable to query source '%.*ls' err %x\n", name_len/sizeof(WCHAR), name, err)); xFsClose(mfd); return err; } // get objectid and set the ea stuff FsInitEa(&xattr); FsInitEaFid(&xattr, fs_id); err = xFsQueryObjectId(mfd, (PVOID) fs_id); if (err == STATUS_SUCCESS) { UINT32 disp = FILE_CREATE; err = xFsCreate(&fd, vfd, name, name_len, (attr.FileAttributes & FILE_ATTRIBUTE_DIRECTORY ? FILE_DIRECTORY_FILE : 0), attr.FileAttributes, FILE_SHARE_READ, &disp, FILE_GENERIC_EXECUTE | FILE_GENERIC_WRITE, (PVOID) &xattr, sizeof(xattr)); if (err == STATUS_SUCCESS) { assert(disp == FILE_CREATED); // if file we need to copy data and set access flags if (!(attr.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { int buflen = sizeof(buf); LARGE_INTEGER off; off.LowPart = 0; off.HighPart = 0; while (1) { err = NtReadFile(mfd, NULL, NULL, NULL, &ios, buf, buflen, &off, NULL); if (err == STATUS_PENDING) { EventWait(mfd); err = ios.Status; } if (err != STATUS_SUCCESS) { break; } err = NtWriteFile(fd, NULL, NULL, NULL, &ios, buf, (ULONG)ios.Information, &off, NULL); if (err == STATUS_PENDING) { EventWait(fd); err = ios.Status; } if (err != STATUS_SUCCESS) { break; } off.LowPart += (ULONG) ios.Information; } // adjust return code if (err == STATUS_END_OF_FILE) { err = STATUS_SUCCESS; } FsLog(("FsDupFile: '%.*ls' err %x\n", name_len/sizeof(WCHAR), name, err)); } else if (flag == TRUE) { // call enum again err = _xFsEnumTree(mfd, XFS_ENUM_FIRST, xFsCopy, (PVOID) fd); } // set new file attributes new_attr.CreationTime = attr.CreationTime; new_attr.LastAccessTime = attr.LastAccessTime; new_attr.LastWriteTime = attr.LastWriteTime; new_attr.ChangeTime = attr.ChangeTime; new_attr.FileAttributes = attr.FileAttributes; err = xFsSetAttr(fd, &new_attr); // close new file xFsClose(fd); } if (err != STATUS_SUCCESS) FsLog(("FsDupFile: unable to open/reset attr '%S' err %x\n", name, err)); } else { FsLog(("FsDupFile: unable to query sid '%S' err %x, skip!\n", name, err)); err = STATUS_SUCCESS; } // close master file xFsClose(mfd); return err; }