diff --git a/nuttx/ChangeLog b/nuttx/ChangeLog index c6d46b359..060eb3f01 100644 --- a/nuttx/ChangeLog +++ b/nuttx/ChangeLog @@ -141,5 +141,7 @@ 0.2.6 2007-xx-xx Gregory Nutt * Added unlink(), mkdir(), rmdir(), and rename() + * Fixed several serious FAT errors with oflags handling (&& instead of &) + * Added FAT support for unlink() and rmdir() * Started m68322 diff --git a/nuttx/Documentation/NuttX.html b/nuttx/Documentation/NuttX.html index 726a5a5c5..49db96df2 100644 --- a/nuttx/Documentation/NuttX.html +++ b/nuttx/Documentation/NuttX.html @@ -186,6 +186,9 @@ The 8th release of NuttX (nuttx-0.2.5) is available for download from the SourceForge website. + The change log associated with the release is available here. + Unreleased changes after this release are avalable in CVS. + These unreleased changes are listed here.

@@ -376,6 +379,35 @@ Other memory:
+
+ + + + + + + + + + + + +
+ Change Logs for Older Releases
+
+ ChangeLog for Current Release
+
+ Unreleased Changes +
+ + + + + + +
+ +
+ ChangeLog for Current Release +
+ +
+ + + + +
+ Unreleased Changes +
+ +
diff --git a/nuttx/arch/sim/src/up_deviceimage.c b/nuttx/arch/sim/src/up_deviceimage.c index 9a28f4edd..70dfc9cab 100644 --- a/nuttx/arch/sim/src/up_deviceimage.c +++ b/nuttx/arch/sim/src/up_deviceimage.c @@ -82,7 +82,11 @@ * xxd -g 1 nuttx-test.vfat.gz >some-file * * Then manually massaged from the gzip xxd output to zlib format. See - * http://www.faqs.org/rfcs/rfc1952.html + * http://www.faqs.org/rfcs/rfc1952.html. This amounts to: + * + * Remove all of the leading bytes through the null terminator of the file name + * Remove the last 8 bytes + * Add 0x08, 0x1d to the beginning. */ static const unsigned char g_vfatdata[] = diff --git a/nuttx/examples/mount/mount_main.c b/nuttx/examples/mount/mount_main.c index 09dc8e189..75cc3c2ce 100644 --- a/nuttx/examples/mount/mount_main.c +++ b/nuttx/examples/mount/mount_main.c @@ -63,6 +63,7 @@ static const char g_source[] = "/dev/blkdev"; static const char g_target[] = "/mnt/fs"; static const char g_filesystemtype[] = "vfat"; +static const char g_testdir1[] = "/mnt/fs/TestDir"; static const char g_testfile1[] = "/mnt/fs/TestDir/TestFile.txt"; static const char g_testfile2[] = "/mnt/fs/TestDir/WritTest.txt"; static const char g_testmsg[] = "This is a write test"; @@ -93,6 +94,8 @@ int user_start(int argc, char *argv[]) int nbytes; int ret; + /* Mount the test file system (see arch/sim/src/up_deviceimage.c */ + printf("main: mounting %s filesystem at target=%s with source=%s\n", g_filesystemtype, g_target, g_source); @@ -101,12 +104,15 @@ int user_start(int argc, char *argv[]) if (ret == 0) { + /* Read a test file that is already on the test file system image */ + printf("main: opening %s for reading\n", g_testfile1); int fd = open(g_testfile1, O_RDONLY); if (fd < 0) { - printf("main: failed to open %s, errno=%d\n", g_testfile1, *get_errno_ptr()); + printf("main: ERROR failed to open %s, errno=%d\n", + g_testfile1, *get_errno_ptr()); } else { @@ -114,7 +120,8 @@ int user_start(int argc, char *argv[]) nbytes = read(fd, buffer, 128); if (nbytes < 0) { - printf("main: failed to read from %s, errno=%d\n", g_testfile1, *get_errno_ptr()); + printf("main: ERROR failed to read from %s, errno=%d\n", + g_testfile1, *get_errno_ptr()); } else { @@ -124,19 +131,23 @@ int user_start(int argc, char *argv[]) close(fd); } + /* Write a test file into a pre-existing file on the test file system */ + printf("main: opening %s for writing\n", g_testfile2); fd = open(g_testfile2, O_WRONLY|O_CREAT|O_TRUNC, 0644); if (fd < 0) { - printf("main: failed to open %s for writing, errno=%d\n", g_testfile2, *get_errno_ptr()); + printf("main: ERROR failed to open %s for writing, errno=%d\n", + g_testfile2, *get_errno_ptr()); } else { int nbytes = write(fd, g_testmsg, strlen(g_testmsg)); if (nbytes < 0) { - printf("main: failed to write to %s, errno=%d\n", g_testfile2, *get_errno_ptr()); + printf("main: ERROR failed to write to %s, errno=%d\n", + g_testfile2, *get_errno_ptr()); } else { @@ -145,12 +156,15 @@ int user_start(int argc, char *argv[]) close(fd); } + /* Read the file that we just wrote */ + printf("main: opening %s for reading\n", g_testfile2); fd = open(g_testfile2, O_RDONLY); if (fd < 0) { - printf("main: failed to open %s for reading, errno=%d\n", g_testfile2, *get_errno_ptr()); + printf("main: ERRORfailed to open %s for reading, errno=%d\n", + g_testfile2, *get_errno_ptr()); } else { @@ -158,7 +172,8 @@ int user_start(int argc, char *argv[]) nbytes = read(fd, buffer, 128); if (nbytes < 0) { - printf("main: failed to read from %s, errno=%d\n", g_testfile2, *get_errno_ptr()); + printf("main: ERROR failed to read from %s, errno=%d\n", + g_testfile2, *get_errno_ptr()); } else { @@ -168,8 +183,124 @@ int user_start(int argc, char *argv[]) close(fd); } + /* Try rmdir() against a file on the directory. It should fail with ENOTDIR */ + + printf("main: Try rmdir(%s)\n", g_testfile1); + + ret = rmdir(g_testfile1); + if (ret == 0) + { + printf("main: ERROR rmdir(%s) succeeded\n", g_testfile1); + } + else if (*get_errno_ptr() != ENOTDIR) + { + printf("main: ERROR rmdir(%s) failed with errno=%d\n", + g_testfile1, *get_errno_ptr()); + } + + /* Try rmdir() against the test directory. It should fail with ENOTEMPTY */ + + printf("main: Try rmdir(%s)\n", g_testdir1); + + ret = rmdir(g_testdir1); + if (ret == 0) + { + printf("main: ERROR rmdir(%s) succeeded\n", g_testdir1); + } + else if (*get_errno_ptr() != ENOTEMPTY) + { + printf("main: ERROR rmdir(%s) failed with errno=%d\n", + g_testdir1, *get_errno_ptr()); + } + + /* Try unlink() against the test directory. It should fail with EISDIR */ + + printf("main: Try unlink(%s)\n", g_testdir1); + + ret = unlink(g_testdir1); + if (ret == 0) + { + printf("main: ERROR unlink(%s) succeeded\n", g_testdir1); + } + else if (*get_errno_ptr() != EISDIR) + { + printf("main: ERROR unlink(%s) failed with errno=%d\n", + g_testdir1, *get_errno_ptr()); + } + + /* Try unlink() against the test file1. It should succeed. */ + + printf("main: Try unlink(%s)\n", g_testfile1); + + ret = unlink(g_testfile1); + if (ret != 0) + { + printf("main: ERROR unlink(%s) failed with errno=%d\n", + g_testfile1, *get_errno_ptr()); + } + + /* Attempt to open testfile1 should fail with ENOENT */ + + printf("main: Try open(%s) for reading\n", g_testfile1); + + fd = open(g_testfile1, O_RDONLY); + if (fd >= 0) + { + printf("main: ERROR open(%s) succeeded\n", g_testfile1); + close(fd); + } + else if (*get_errno_ptr() != ENOENT) + { + printf("main: ERROR open(%s) failed with errno=%d\n", + g_testfile1, *get_errno_ptr()); + } + + /* Try rmdir() against the test directory. It should still fail with ENOTEMPTY */ + + printf("main: Try rmdir(%s)\n", g_testdir1); + + ret = rmdir(g_testdir1); + if (ret == 0) + { + printf("main: ERROR rmdir(%s) succeeded\n", g_testdir1); + } + else if (*get_errno_ptr() != ENOTEMPTY) + { + printf("main: ERROR rmdir(%s) failed with errno=%d\n", + g_testdir1, *get_errno_ptr()); + } + + /* Try unlink() against the test file2. It should succeed. */ + + printf("main: Try unlink(%s)\n", g_testfile2); + + ret = unlink(g_testfile2); + if (ret != 0) + { + printf("main: ERROR unlink(%s) failed with errno=%d\n", + g_testfile2, *get_errno_ptr()); + } + + /* Try rmdir() against the test directory. It should now succeed. */ + + printf("main: Try rmdir(%s)\n", g_testdir1); + + ret = rmdir(g_testdir1); + if (ret != 0) + { + printf("main: ERROR rmdir(%s) failed with errno=%d\n", + g_testdir1, *get_errno_ptr()); + } + + /* Unmount the file system */ + + printf("main: Try unmount(%s)\n", g_target); + ret = umount(g_target); - printf("main: umount() returned %d\n", ret); + if (ret != 0) + { + printf("main: ERROR umount() failed, errno %d\n", *get_errno_ptr()); + } } fflush(stdout); diff --git a/nuttx/fs/fs_fat32.c b/nuttx/fs/fs_fat32.c index 81992e72d..d0561f380 100644 --- a/nuttx/fs/fs_fat32.c +++ b/nuttx/fs/fs_fat32.c @@ -73,7 +73,7 @@ * Private Function Prototypes ****************************************************************************/ -static int fat_open(FAR struct file *filp, const char *rel_path, +static int fat_open(FAR struct file *filp, const char *relpath, int oflags, mode_t mode); static int fat_close(FAR struct file *filp); static ssize_t fat_read(FAR struct file *filp, char *buffer, size_t buflen); @@ -86,10 +86,10 @@ static int fat_sync(FAR struct file *filp); static int fat_bind(FAR struct inode *blkdriver, const void *data, void **handle); static int fat_unbind(void *handle); -static int fat_unlink(struct inode *mountpt, const char *rel_path); -static int fat_mkdir(struct inode *mountpt, const char *rel_path, +static int fat_unlink(struct inode *mountpt, const char *relpath); +static int fat_mkdir(struct inode *mountpt, const char *relpath, mode_t mode); -static int fat_rmdir(struct inode *mountpt, const char *rel_path); +static int fat_rmdir(struct inode *mountpt, const char *relpath); static int fat_rename(struct inode *mountpt, const char *old_relpath, const char *new_relpath); @@ -120,6 +120,7 @@ const struct mountpt_operations fat_operations = fat_unbind, fat_unlink, fat_mkdir, + fat_rmdir, fat_rename }; @@ -131,7 +132,7 @@ const struct mountpt_operations fat_operations = * Name: fat_open ****************************************************************************/ -static int fat_open(FAR struct file *filp, const char *rel_path, +static int fat_open(FAR struct file *filp, const char *relpath, int oflags, mode_t mode) { struct fat_dirinfo_s dirinfo; @@ -165,13 +166,12 @@ static int fat_open(FAR struct file *filp, const char *rel_path, /* Initialize the directory info structure */ memset(&dirinfo, 0, sizeof(struct fat_dirinfo_s)); - dirinfo.fs = fs; /* Locate the directory entry for this path */ - ret = fat_finddirentry(&dirinfo, rel_path); + ret = fat_finddirentry(fs, &dirinfo, relpath); - /* Three possibililities: (1) a node exists for the rel_path and + /* Three possibililities: (1) a node exists for the relpath and * dirinfo describes the directory entry of the entity, (2) the * node does not exist, or (3) some error occurred. */ @@ -206,7 +206,7 @@ static int fat_open(FAR struct file *filp, const char *rel_path, /* Check if the caller has sufficient privileges to open the file */ readonly = ((DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_READONLY) != 0); - if (((oflags && O_WRONLY) != 0) && readonly) + if (((oflags & O_WRONLY) != 0) && readonly) { ret = -EACCES; goto errout_with_semaphore; @@ -236,7 +236,7 @@ static int fat_open(FAR struct file *filp, const char *rel_path, { /* The file does not exist. Were we asked to create it? */ - if ((oflags && O_CREAT) == 0) + if ((oflags & O_CREAT) == 0) { /* No.. then we fail with -ENOENT */ ret = -ENOENT; @@ -303,7 +303,7 @@ static int fat_open(FAR struct file *filp, const char *rel_path, /* In write/append mode, we need to set the file pointer to the end of the file */ - if ((oflags && (O_APPEND|O_WRONLY)) == (O_APPEND|O_WRONLY)) + if ((oflags & (O_APPEND|O_WRONLY)) == (O_APPEND|O_WRONLY)) { ff->ff_position = ff->ff_size; } @@ -1320,7 +1320,7 @@ static int fat_unbind(void *handle) * ****************************************************************************/ -static int fat_unlink(struct inode *mountpt, const char *rel_path) +static int fat_unlink(struct inode *mountpt, const char *relpath) { struct fat_mountpt_s *fs; int ret; @@ -1337,15 +1337,20 @@ static int fat_unlink(struct inode *mountpt, const char *rel_path) fat_semtake(fs); ret = fat_checkmount(fs); - if (ret != OK) + if (ret == OK) { - goto errout_with_semaphore; + /* If the file is open, the correct behavior is to remove the file + * name, but to keep the file cluster chain in place until the last + * open reference to the file is closed. + */ + +#warning "Need to defer deleting cluster chain if the file is open" + + /* Remove the file */ + + ret = fat_remove(fs, relpath, FALSE); } -#warning "fat_unlink is not implemented" - ret = -ENOSYS; - - errout_with_semaphore: fat_semgive(fs); return ret; } @@ -1357,7 +1362,7 @@ static int fat_unlink(struct inode *mountpt, const char *rel_path) * ****************************************************************************/ -static int fat_mkdir(struct inode *mountpt, const char *rel_path, mode_t mode) +static int fat_mkdir(struct inode *mountpt, const char *relpath, mode_t mode) { struct fat_mountpt_s *fs; int ret; @@ -1394,7 +1399,7 @@ static int fat_mkdir(struct inode *mountpt, const char *rel_path, mode_t mode) * ****************************************************************************/ -int fat_rmdir(struct inode *mountpt, const char *rel_path) +int fat_rmdir(struct inode *mountpt, const char *relpath) { struct fat_mountpt_s *fs; int ret; @@ -1411,15 +1416,20 @@ int fat_rmdir(struct inode *mountpt, const char *rel_path) fat_semtake(fs); ret = fat_checkmount(fs); - if (ret != OK) + if (ret == OK) { - goto errout_with_semaphore; + /* If the directory is open, the correct behavior is to remove the directory + * name, but to keep the directory cluster chain in place until the last + * open reference to the directory is closed. + */ + +#warning "Need to defer deleting cluster chain if the directory is open" + + /* Remove the directory */ + + ret = fat_remove(fs, relpath, TRUE); } -#warning "fat_rmdir is not implemented" - ret = -ENOSYS; - - errout_with_semaphore: fat_semgive(fs); return ret; } diff --git a/nuttx/fs/fs_fat32.h b/nuttx/fs/fs_fat32.h index 9e10cd7d8..e8edd14c7 100644 --- a/nuttx/fs/fs_fat32.h +++ b/nuttx/fs/fs_fat32.h @@ -488,7 +488,6 @@ struct fat_file_s struct fat_dirinfo_s { - struct fat_mountpt_s *fs; /* Pointer to the parent mountpoint */ ubyte fd_name[8+3]; /* Filename -- directory format*/ #ifdef CONFIG_FAT_LCNAMES ubyte fd_ntflags; /* NTRes lower case flags */ @@ -557,15 +556,17 @@ EXTERN sint32 fat_extendchain(struct fat_mountpt_s *fs, uint32 cluster); /* Help for traverseing directory trees */ -EXTERN int fat_nextdirentry(struct fat_dirinfo_s *dirinfo); -EXTERN int fat_finddirentry(struct fat_dirinfo_s *dirinfo, const char *path); +EXTERN int fat_nextdirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo); +EXTERN int fat_finddirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo, + const char *path); -/* File creation helpers */ +/* File creation and removal helpers */ EXTERN int fat_dirtruncate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo); EXTERN int fat_dircreate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo); +EXTERN int fat_remove(struct fat_mountpt_s *fs, const char *relpath, boolean directory); -/* Mountpoint and fFile buffer cache (for partial sector accesses) */ +/* Mountpoint and file buffer cache (for partial sector accesses) */ EXTERN int fat_fscacheread(struct fat_mountpt_s *fs, size_t sector); EXTERN int fat_ffcacheflush(struct fat_mountpt_s *fs, struct fat_file_s *ff); diff --git a/nuttx/fs/fs_fat32util.c b/nuttx/fs/fs_fat32util.c index 826d13bec..eb7981a56 100644 --- a/nuttx/fs/fs_fat32util.c +++ b/nuttx/fs/fs_fat32util.c @@ -471,7 +471,7 @@ static int fat_allocatedirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s * return OK; } - ret = fat_nextdirentry(dirinfo); + ret = fat_nextdirentry(fs, dirinfo); if (ret < 0) { return ret; @@ -1583,9 +1583,8 @@ sint32 fat_extendchain(struct fat_mountpt_s *fs, uint32 cluster) * ****************************************************************************/ -int fat_nextdirentry(struct fat_dirinfo_s *dirinfo) +int fat_nextdirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) { - struct fat_mountpt_s *fs = dirinfo->fs; unsigned int cluster; unsigned int ndx; @@ -1674,9 +1673,9 @@ int fat_nextdirentry(struct fat_dirinfo_s *dirinfo) * ****************************************************************************/ -int fat_finddirentry(struct fat_dirinfo_s *dirinfo, const char *path) +int fat_finddirentry(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo, + const char *path) { - struct fat_mountpt_s *fs = dirinfo->fs; size_t cluster; ubyte *direntry = NULL; char terminator; @@ -1778,7 +1777,7 @@ int fat_finddirentry(struct fat_dirinfo_s *dirinfo, const char *path) /* No... get the next directory index and try again */ - if (fat_nextdirentry(dirinfo) != OK) + if (fat_nextdirentry(fs, dirinfo) != OK) { return -ENOENT; } @@ -1935,6 +1934,184 @@ int fat_dircreate(struct fat_mountpt_s *fs, struct fat_dirinfo_s *dirinfo) return OK; } +/**************************************************************************** + * Name: fat_remove + * + * Desciption: Remove a directory or file from the file system. This + * implements both rmdir() and unlink(). + * + ****************************************************************************/ + +int fat_remove(struct fat_mountpt_s *fs, const char *relpath, boolean directory) +{ + struct fat_dirinfo_s dirinfo; + uint32 dircluster; + size_t dirsector; + int ret; + + /* Find the directory entry referring to the entry to be deleted */ + + ret = fat_finddirentry(fs, &dirinfo, relpath); + if (ret != OK) + { + /* No such path */ + + return -ENOENT; + } + + /* Check if this is a FAT12/16 root directory */ + + if (dirinfo.fd_entry == NULL) + { + /* The root directory cannot be removed */ + + return -EPERM; + } + + /* The object has to have write access to be deleted */ + + if ((DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_READONLY) != 0) + { + /* It is a read-only entry */ + + return -EACCES; + } + + /* Get the directory sector and cluster containing the + * entry to be deleted + */ + + dirsector = fs->fs_currentsector; + dircluster = + ((uint32)DIR_GETFSTCLUSTHI(dirinfo.fd_entry) << 16) | + DIR_GETFSTCLUSTLO(dirinfo.fd_entry); + + /* Is this entry a directory? */ + + if (DIR_GETATTRIBUTES(dirinfo.fd_entry) & FATATTR_DIRECTORY) + { + /* It is a sub-directory. Check if we are be asked to remove + * a directory or a file. + */ + + if (!directory) + { + /* We are asked to delete a file */ + + return -EISDIR; + } + + /* We are asked to delete a directory. Check if this + * sub-directory is empty + */ + + dirinfo.fd_currcluster = dircluster; + dirinfo.fd_currsector = fat_cluster2sector(fs, dircluster); + dirinfo.fd_index = 2; + + /* Loop until either (1) an entry is found in the directory + * (error), (2) the directory is found to be empty, or (3) some + * error occurs. + */ + + for (;;) + { + unsigned int subdirindex; + ubyte *subdirentry; + + /* Make sure that the sector containing the of the + * subdirectory sector is in the cache + */ + + ret = fat_fscacheread(fs, dirinfo.fd_currsector); + if (ret < 0) + { + return ret; + } + + /* Get a reference to the next entry in the directory */ + + subdirindex = (dirinfo.fd_index & DIRSEC_NDXMASK(fs)) * 32; + subdirentry = &fs->fs_buffer[subdirindex]; + + /* Is this the last entry in the direcory? */ + + if (subdirentry[DIR_NAME] == DIR0_ALLEMPTY) + { + /* Yes then the directory is empty. Break out of the + * loop and delete the directory. + */ + + break; + } + + /* Check if the next entry refers to a file or directory */ + + if (subdirentry[DIR_NAME] != DIR0_EMPTY && + !(DIR_GETATTRIBUTES(subdirentry) & FATATTR_VOLUMEID)) + { + /* The directory is not empty */ + + return -ENOTEMPTY; + } + + /* Get the next directgory entry */ + + ret = fat_nextdirentry(fs, &dirinfo); + if (ret < 0) + { + return ret; + } + } + } + else + { + /* It is a file. Check if we are be asked to remove a directory + * or a file. + */ + + if (directory) + { + /* We are asked to remove a directory */ + + return -ENOTDIR; + } + } + + /* Make sure that the directory containing the entry to be deleted is + * in the cache. + */ + + ret = fat_fscacheread(fs, dirsector); + if (ret < 0) + { + return ret; + } + + /* Mark the directory entry 'deleted' */ + + dirinfo.fd_entry[DIR_NAME] = DIR0_EMPTY; + fs->fs_dirty = TRUE; + + /* And remove the cluster chain making up the subdirectory */ + + ret = fat_removechain(fs, dircluster); + if (ret < 0) + { + return ret; + } + + /* Update the FSINFO sector (FAT32) */ + + ret = fat_updatefsinfo(fs); + if (ret < 0) + { + return ret; + } + + return OK; +} + /**************************************************************************** * Name: fat_fscacheread * @@ -2092,7 +2269,7 @@ int fat_ffcacheinvalidate(struct fat_mountpt_s *fs, struct fat_file_s *ff) * Name: fat_updatefsinfo * * Desciption: Flush evertyhing buffered for the mountpoint and update - * the FSINFO sector, if appropriate + * the FSINFO sector, if appropriate * ****************************************************************************/ diff --git a/nuttx/include/nuttx/fs.h b/nuttx/include/nuttx/fs.h index 639f68856..42ba19661 100644 --- a/nuttx/include/nuttx/fs.h +++ b/nuttx/include/nuttx/fs.h @@ -122,7 +122,7 @@ struct mountpt_operations * information to manage privileges. */ - int (*open)(FAR struct file *filp, const char *rel_path, + int (*open)(FAR struct file *filp, const char *relpath, int oflags, mode_t mode); /* The following methods must be identical in signature and position because @@ -150,10 +150,10 @@ struct mountpt_operations int (*bind)(FAR struct inode *blkdriver, const void *data, void **handle); int (*unbind)(void *handle); - int (*unlink)(struct inode *mountpt, const char *rel_path); - int (*mkdir)(struct inode *mountpt, const char *rel_path, mode_t mode); - int (*rmdir)(struct inode *mountpt, const char *rel_path); - int (*rename)(struct inode *mountpt, const char *old_relpath, const char *new_relpath); + int (*unlink)(struct inode *mountpt, const char *relpath); + int (*mkdir)(struct inode *mountpt, const char *relpath, mode_t mode); + int (*rmdir)(struct inode *mountpt, const char *relpath); + int (*rename)(struct inode *mountpt, const char *oldrelpath, const char *newrelpath); /* NOTE: More operations will be needed here to support: disk usage stats * file stat(), file attributes, file truncation, etc.