C on amd64 Linux, 36 bytes (timestamp only), 52 49 bytes (real disk activity)
I hard-code the open(2)
flags, so this is not portable to other ABIs. Linux on other platforms likely uses the same O_TRUNC
, etc., but other POSIX OSes may not.
+4 bytes to pass a correct permission arg to make sure the file is created with owner write access, see below. (This happens to work with gcc 5.2)
somewhat-portable ANSI C, 38/51 bytes (timestamp only), 52/67 bytes (real disk activity)
Based on @Cat's answer, with a tip from @Jens.
The first number is for implementations where an int
can hold FILE *fopen()
's return value, second number if we can't do that. On Linux, heap addresses happen to be in the low 32 bits of address space, so it works even without -m32
or -mx32
. (Declaring void*fopen();
is shorter than #include <stdio.h>
)
Timestamp metadata I/O only:
main(){for(;;)close(open("a",577));} // Linux x86-64
//void*fopen(); // compile with -m32 or -mx32 or whatever, so an int holds a pointer.
main(){for(;;)fclose(fopen("a","w"));}
Writing a byte, actually hitting the disk on Linux 4.2.0 + XFS + lazytime
:
main(){for(;write(open("a",577),"",1);close(3));}
write
is the for-loop condition, which is fine since it always returns 1. close
is the increment.
// semi-portable: storing a FILE* in an int. Works on many systems
main(f){for(;f=fopen("a","w");fclose(f))fputc(0,f);} // 52 bytes
// Should be highly portable, except to systems that require prototypes for all functions.
void*f,*fopen();main(){for(;f=fopen("a","w");fclose(f))fputc(0,f);} // 67 bytes
Explanation of the non-portable version:
The file is created with random garbage permissions. With gcc
5.2, with -O0
or -O3
, it happens to include owner write permission, but this is not guaranteed. 0666
is decimal 438. A 3rd arg to open
would take another 4 bytes. We're already hard-coding O_TRUNC and so on, but this could break with a different compiler or libc on the same ABI.
We can't omit the 2nd arg to open
, because the garbage value happens to include O_EXCL
, and O_TRUNC|O_APPEND
, so open fails with EINVAL
.
We don't need to save the return value from open()
. We assume it's 3
, because it always will be. Even if we start with fd 3 open, it will be closed after the first iteration. Worst-case, open
keeps opening new fds until 3 is the last available file descriptor. So, up to the first 65531 write()
calls could fail with EBADF
, but will then work normally with every open
creating fd = 3.
577 = 0x241 = O_WRONLY|O_CREAT|O_TRUNC
on x86-64 Linux. Without O_TRUNC
, the inode mod time and change time aren't updated, so a shorter arg isn't possible. O_TRUNC
is still essential for the version that calls write
to produce actual disk activity, not rewrite in place.
I see some answers that open("a",1)
. O_CREAT is required if a
doesn't already exist. O_CREAT
is defined as octal 0100 (64, 0x40) on Linux.
No resource leaks, so it can run forever. strace
output:
open("a", O_WRONLY|O_CREAT|O_TRUNC, 03777762713526650) = 3
close(3) = 0
... repeating
or
open("a", O_WRONLY|O_CREAT|O_TRUNC, 01) = 3
write(3, "\0", 1) = 1 # This is the terminating 0 byte in the empty string we pass to write(2)
close(3) = 0
I got the decimal value of the open
flags for this ABI using strace -eraw=open
on my C++ version.
On a filesystem with the Linux lazytime
mount option enabled, a change that only affects inode timestamps will only cause one write per 24 hours. With that mount option disabled, timestamp updating might be a viable way to wear out your SSD. (However, several other answers only do metadata I/O).
alternatives:
shorter non-working:
main(){for(;;)close(write(open("a",577),"",3));}
uses write
's return value to pass a 3
arg to close. It saves another byte, but doesn't work with gcc -O0 or -O3 on amd64. The garbage in the 3rd arg to open
is different, and doesn't include write permission. a
gets created the first time, but future iterations all fail with -EACCESS
.
longer, working, with different system calls:
main(c){for(open("a",65);pwrite(3,"",1);)sync();}
rewrites a byte in-place and calls sync()
to sync all filesystems system-wide. This keeps the drive light lit up.
We don't care which byte, so we don't pass 4th arg to pwrite. Yay for sparse files:
$ ll -s a
300K -rwx-wx--- 1 peter peter 128T May 15 11:43 a
Writing one byte at an offset of ~128TiB led to xfs using 300kiB of space to hold the extent map, I guess. Don't try this on OS X with HFS+: IIRC, HFS+ doesn't support sparse files, so it will fill the disk.
XFS is a proper 64bit filesystem, supporting individual files up to 8 exabytes. i.e. 2^63-1, the maximum value off_t
can hold.
strace
output:
open("a", O_WRONLY|O_CREAT, 03777711166007270) = 3
pwrite(3, "\0", 1, 139989929353760) = 1
sync() = 0
pwrite(3, "\0", 1, 139989929380071) = 1
sync() = 0
...
/dev/null
? (Isyes>/dev/null
a valid Bash answer?) \$\endgroup\$