/** * ntfsfile.c - devoptab file routines for NTFS-based devices. * * Copyright (c) 2010 Dimok * Copyright (c) 2009 Rhys "Shareese" Koedijk * Copyright (c) 2006 Michael "Chishm" Chisholm * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program/include file is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_STRING_H #include #endif #include "ntfsinternal.h" #include "ntfsfile.h" #define STATE(x) ((ntfs_file_state*)x) void ntfsCloseFile ( ntfs_file_state *file ) { // Sanity check if ( !file || !file->vd ) return; // Special case fix ups for compressed and/or encrypted files if ( file->compressed ) ntfs_attr_pclose( file->data_na ); #ifdef HAVE_SETXATTR if ( file->encrypted ) ntfs_efs_fixup_attribute( NULL, file->data_na ); #endif // Close the file data attribute (if open) if ( file->data_na ) ntfs_attr_close( file->data_na ); // Sync the file (and its attributes) to disc if ( file->write ) { ntfsUpdateTimes( file->vd, file->ni, NTFS_UPDATE_ATIME | NTFS_UPDATE_CTIME ); ntfsSync( file->vd, file->ni ); } if ( file->read ) ntfsUpdateTimes( file->vd, file->ni, NTFS_UPDATE_ATIME ); // Close the file (if open) if ( file->ni ) ntfsCloseEntry( file->vd, file->ni ); // Reset the file state file->ni = NULL; file->data_na = NULL; file->flags = 0; file->read = false; file->write = false; file->append = false; file->pos = 0; file->len = 0; return; } int ntfs_open_r ( struct _reent *r, void *fileStruct, const char *path, int flags, int mode ) { ntfs_log_trace( "fileStruct %p, path %s, flags %i, mode %i\n", fileStruct, path, flags, mode ); ntfs_file_state* file = STATE( fileStruct ); // Get the volume descriptor for this path file->vd = ntfsGetVolume( path ); if ( !file->vd ) { r->_errno = ENODEV; return -1; } // Lock ntfsLock( file->vd ); // Determine which mode the file is opened for file->flags = flags; if ( ( flags & 0x03 ) == O_RDONLY ) { file->read = true; file->write = false; file->append = false; } else if ( ( flags & 0x03 ) == O_WRONLY ) { file->read = false; file->write = true; file->append = ( flags & O_APPEND ); } else if ( ( flags & 0x03 ) == O_RDWR ) { file->read = true; file->write = true; file->append = ( flags & O_APPEND ); } else { r->_errno = EACCES; ntfsUnlock( file->vd ); return -1; } // Try and find the file and (if found) ensure that it is not a directory file->ni = ntfsOpenEntry( file->vd, path ); if ( file->ni && ( file->ni->mrec->flags & MFT_RECORD_IS_DIRECTORY ) ) { ntfsCloseEntry( file->vd, file->ni ); ntfsUnlock( file->vd ); r->_errno = EISDIR; return -1; } // Are we creating this file? if ( flags & O_CREAT ) { // The file SHOULD NOT already exist if ( file->ni ) { ntfsCloseEntry( file->vd, file->ni ); ntfsUnlock( file->vd ); r->_errno = EEXIST; return -1; } // Create the file file->ni = ntfsCreate( file->vd, path, S_IFREG, NULL ); if ( !file->ni ) { ntfsUnlock( file->vd ); return -1; } } // Sanity check, the file should be open by now if ( !file->ni ) { ntfsUnlock( file->vd ); r->_errno = ENOENT; return -1; } // Open the files data attribute file->data_na = ntfs_attr_open( file->ni, AT_DATA, AT_UNNAMED, 0 ); if ( !file->data_na ) { ntfsCloseEntry( file->vd, file->ni ); ntfsUnlock( file->vd ); return -1; } // Determine if this files data is compressed and/or encrypted file->compressed = NAttrCompressed( file->data_na ) || ( file->ni->flags & FILE_ATTR_COMPRESSED ); file->encrypted = NAttrEncrypted( file->data_na ) || ( file->ni->flags & FILE_ATTR_ENCRYPTED ); // We cannot read/write encrypted files if ( file->encrypted ) { ntfs_attr_close( file->data_na ); ntfsCloseEntry( file->vd, file->ni ); ntfsUnlock( file->vd ); r->_errno = EACCES; return -1; } // Make sure we aren't trying to write to a read-only file if ( ( file->ni->flags & FILE_ATTR_READONLY ) && file->write ) { ntfs_attr_close( file->data_na ); ntfsCloseEntry( file->vd, file->ni ); ntfsUnlock( file->vd ); r->_errno = EROFS; return -1; } // Truncate the file if requested if ( ( flags & O_TRUNC ) && file->write ) { if ( ntfs_attr_truncate( file->data_na, 0 ) ) { ntfs_attr_close( file->data_na ); ntfsCloseEntry( file->vd, file->ni ); ntfsUnlock( file->vd ); r->_errno = errno; return -1; } } // Set the files current position and length file->pos = 0; file->len = file->data_na->data_size; ntfs_log_trace( "file->len %d\n", file->len ); // Update file times ntfsUpdateTimes( file->vd, file->ni, NTFS_UPDATE_ATIME ); // Insert the file into the double-linked FILO list of open files if ( file->vd->firstOpenFile ) { file->nextOpenFile = file->vd->firstOpenFile; file->vd->firstOpenFile->prevOpenFile = file; } else { file->nextOpenFile = NULL; } file->prevOpenFile = NULL; file->vd->firstOpenFile = file; file->vd->openFileCount++; // Unlock ntfsUnlock( file->vd ); return ( int )fileStruct; } int ntfs_close_r ( struct _reent *r, int fd ) { ntfs_log_trace( "fd %p\n", fd ); ntfs_file_state* file = STATE( fd ); // Sanity check if ( !file || !file->vd ) { r->_errno = EBADF; return -1; } // Lock ntfsLock( file->vd ); // Close the file ntfsCloseFile( file ); // Remove the file from the double-linked FILO list of open files file->vd->openFileCount--; if ( file->nextOpenFile ) file->nextOpenFile->prevOpenFile = file->prevOpenFile; if ( file->prevOpenFile ) file->prevOpenFile->nextOpenFile = file->nextOpenFile; else file->vd->firstOpenFile = file->nextOpenFile; // Unlock ntfsUnlock( file->vd ); return 0; } ssize_t ntfs_write_r ( struct _reent *r, int fd, const char *ptr, size_t len ) { ntfs_log_trace( "fd %p, ptr %p, len %Li\n", fd, ptr, len ); ntfs_file_state* file = STATE( fd ); ssize_t written = 0; off_t old_pos = 0; // Sanity check if ( !file || !file->vd || !file->ni || !file->data_na ) { r->_errno = EINVAL; return -1; } // Short circuit cases where we don't actually have to do anything if ( !ptr || len <= 0 ) { return 0; } // Lock ntfsLock( file->vd ); // Check that we are allowed to write to this file if ( !file->write ) { ntfsUnlock( file->vd ); r->_errno = EACCES; return -1; } // If we are in append mode, backup the current position and move to the end of the file if ( file->append ) { old_pos = file->pos; file->pos = file->len; } // Write to the files data atrribute while ( len ) { ssize_t ret = ntfs_attr_pwrite( file->data_na, file->pos, len, ptr ); if ( ret <= 0 ) { ntfsUnlock( file->vd ); r->_errno = errno; return -1; } len -= ret; file->pos += ret; written += ret; } // If we are in append mode, restore the current position to were it was prior to this write if ( file->append ) { file->pos = old_pos; } // Mark the file for archiving (if we actually wrote something) if ( written ) file->ni->flags |= FILE_ATTR_ARCHIVE; // Update the files data length file->len = file->data_na->data_size; // Unlock ntfsUnlock( file->vd ); return written; } ssize_t ntfs_read_r ( struct _reent *r, int fd, char *ptr, size_t len ) { ntfs_log_trace( "fd %p, ptr %p, len %Li\n", fd, ptr, len ); ntfs_file_state* file = STATE( fd ); ssize_t read = 0; // Sanity check if ( !file || !file->vd || !file->ni || !file->data_na ) { r->_errno = EINVAL; return -1; } // Short circuit cases where we don't actually have to do anything if ( !ptr || len <= 0 ) { return 0; } // Lock ntfsLock( file->vd ); // Check that we are allowed to read from this file if ( !file->read ) { ntfsUnlock( file->vd ); r->_errno = EACCES; return -1; } // Don't read past the end of file if ( file->pos + len > file->len ) { r->_errno = EOVERFLOW; len = file->len - file->pos; ntfs_log_trace( "EOVERFLOW" ); } ntfs_log_trace( "file->pos:%d, len:%d, file->len:%d \n", ( u32 )file->pos, ( u32 )len, ( u32 )file->len ); // Read from the files data attribute while ( len ) { ssize_t ret = ntfs_attr_pread( file->data_na, file->pos, len, ptr ); if ( ret <= 0 || ret > len ) { ntfsUnlock( file->vd ); r->_errno = errno; return -1; } ptr += ret; len -= ret; file->pos += ret; read += ret; } //ntfs_log_trace("file->pos: %d \n", (u32)file->pos); // Update file times (if we actually read something) // Unlock ntfsUnlock( file->vd ); return read; } off_t ntfs_seek_r ( struct _reent *r, int fd, off_t pos, int dir ) { ntfs_log_trace( "fd %p, pos %Li, dir %i\n", fd, pos, dir ); ntfs_file_state* file = STATE( fd ); off_t position = 0; // Sanity check if ( !file || !file->vd || !file->ni || !file->data_na ) { r->_errno = EINVAL; return -1; } // Lock ntfsLock( file->vd ); // Set the files current position switch ( dir ) { case SEEK_SET: position = file->pos = MIN( MAX( pos, 0 ), file->len ); break; case SEEK_CUR: position = file->pos = MIN( MAX( file->pos + pos, 0 ), file->len ); break; case SEEK_END: position = file->pos = MIN( MAX( file->len + pos, 0 ), file->len ); break; } // Unlock ntfsUnlock( file->vd ); return position; } int ntfs_fstat_r ( struct _reent *r, int fd, struct stat *st ) { ntfs_log_trace( "fd %p\n", fd ); ntfs_file_state* file = STATE( fd ); int ret = 0; // Sanity check if ( !file || !file->vd || !file->ni || !file->data_na ) { r->_errno = EINVAL; return -1; } // Short circuit cases were we don't actually have to do anything if ( !st ) return 0; // Get the file stats ret = ntfsStat( file->vd, file->ni, st ); if ( ret ) r->_errno = errno; return ret; } int ntfs_ftruncate_r ( struct _reent *r, int fd, off_t len ) { ntfs_log_trace( "fd %p, len %Li\n", fd, len ); ntfs_file_state* file = STATE( fd ); // Sanity check if ( !file || !file->vd || !file->ni || !file->data_na ) { r->_errno = EINVAL; return -1; } // Lock ntfsLock( file->vd ); // Check that we are allowed to write to this file if ( !file->write ) { ntfsUnlock( file->vd ); r->_errno = EACCES; return -1; } // For compressed files, only deleting and expanding contents are implemented if ( file->compressed && len > 0 && len < file->data_na->initialized_size ) { ntfsUnlock( file->vd ); r->_errno = EOPNOTSUPP; return -1; } // Resize the files data attribute, either by expanding or truncating if ( file->compressed && len > file->data_na->initialized_size ) { char zero = 0; if ( ntfs_attr_pwrite( file->data_na, len - 1, 1, &zero ) <= 0 ) { ntfsUnlock( file->vd ); r->_errno = errno; return -1; } } else { if ( ntfs_attr_truncate( file->data_na, len ) ) { ntfsUnlock( file->vd ); r->_errno = errno; return -1; } } // Mark the file for archiving (if we actually changed something) if ( file->len != file->data_na->data_size ) file->ni->flags |= FILE_ATTR_ARCHIVE; // Update file times (if we actually changed something) if ( file->len != file->data_na->data_size ) ntfsUpdateTimes( file->vd, file->ni, NTFS_UPDATE_AMCTIME ); // Update the files data length file->len = file->data_na->data_size; // Sync the file (and its attributes) to disc ntfsSync( file->vd, file->ni ); // Unlock ntfsUnlock( file->vd ); return 0; } int ntfs_fsync_r ( struct _reent *r, int fd ) { ntfs_log_trace( "fd %p\n", fd ); ntfs_file_state* file = STATE( fd ); int ret = 0; // Sanity check if ( !file || !file->vd || !file->ni || !file->data_na ) { r->_errno = EINVAL; return -1; } // Lock ntfsLock( file->vd ); // Sync the file (and its attributes) to disc ret = ntfsSync( file->vd, file->ni ); if ( ret ) r->_errno = errno; // Unlock ntfsUnlock( file->vd ); return ret; }