07 - File I-O
Class: CSCE-313
Notes:
Outline:
- UNIX I/O
- Common operations (system calls)
- raed() and write() for basic read and write from/to an I/O device
- open() for access control
- File Descriptors (FD)
Unix I/O
Background
Applications running in the system are addresses space abstractions called processes.
Processes need to talk to the outside world, and they do so via privileged interfaces (system calls)
In this module we will focus on the UNIX I/O interface.
- Transfer happens in bytes
- I/O devices are treated as files for uniformity
- Basic operations are read and write
- Checks are needed to ensure secure access
- Done through open and close
- Session state also created.
Notes:
- Once you know how to deal with a file, everything else is very similar in UNIX
- In UNIX a file is just a sequence of bytes, if there is any structure to those bytes, that is built by an application.
- Example:
- A SQLite database
bookmarks.db - Print the byte string from this database file
- It is just a sequence of bytes, you will be able to see the offset and the bytes
- There is no structure that UNIX OS imposes on this file
- If we wanted to build any structure on this, we would need to use an application which understands this array of bytes
- For example run:
sqlite3 bookmarks.db- Then
.show tablesto show info of this file - It reads the bytes on from the file and lets you interpret them
- Then
- A SQLite database
- Takeaway: In UNIX, files are just a sequence of bytes with no structure, the application needs to read these bytes and apply a structure to them.
- When you open a file for reading or writing, you also create a session for that file
- In particular there is a pointer that the OS will create for you, as you read through the file, the pointer will advance, now you don't need to specify at what number of bytes you left off.
Example:
Look at your hard disk:
ls -l /dev/sda
- It is actually a block device
- Even if you want a single byte from this device you need to read an entire block
What if we cat this file?
cat /dev/sda
- Yes you will get a lot of random bytes in your screen
- Those are the actual bytes of the
/dev/sdadisk
Unix Files
A Unix file is a sequence of bytes
Most I/O devices (networks, disks, terminals) are represented as files.
/dev/sda2(/usrdisk partition;ais the order and #2is the partition)/dev/tty2(terminal)
Even the kernel is represented as a file:
/dev/mem(kernel memory image)/proc(kernel data structures)
All I/O is performed by reading and writing files
Notes:
/dev/pts/0
- It is not a real device, it is a terminal device
Write into this device
echo "Hello World" > /dev/pts/0
Hello World
- It printed to your terminal!
- Even the terminal looks like a file!
You can also cat /dev/mem, which is the physical bytes of your memory
cat /dev/mem
- You could potentially find strings
A /proc filesystem does not need system calls, you can read and write to this filesystem
cat /proc/uptime
- This is a safe way of reading and writing into the kernel without having to increase the number of system calls
- This example will give the uptime of the instance.
- There is a complete space under
/proc- You have your implicit context when you are cat'ing or listing files under
/proc(you can find information about yourself)
- You have your implicit context when you are cat'ing or listing files under
Common Unix File Types
- Regular Files
- File containing user/app data (binary, text, ...)
- Directories
- A file that contains the names and locations of other files
- Structured records returned as type
dirent - Can't be read as a byte stream
- Character special and block special files
- Terminals (character special) and disks (block special)
echo "Hi" > `tty`
- Terminals (character special) and disks (block special)
- FIFO (named pipe)
- A file type used for inter-process communication
- Socket
- A file type used for network communication between processes
- A Unix domain socket has a pathname!
Notes:
- A file type of
-indicates a regular file - A
dindicates directory - You build a complete namespace under
/ - Directory contains an enumeration of all the other things in that directory
- In that sense a directory is like a file, except that the structure of encoding these entries is considered to be so important that UNIX won't let you touch it (r/w to it).
Generally all UNIX systems have a dictionary:
cat /usr/share/dict/words
- Will display all the words of the dictionary
- This is possible because
wordsis a file - It is actually a symlink (symbolic link) which is basically jus ta redirect for the real file?
But can you do?
cat /usr/share/dict
Output:
cat: /usr/share/dict: Is a directory
- You cannot read a 'directory' type file
Now what if we do:
cat /*
- This is not a problem
- Will list all the entries of the current directory
- This happens because the shell is expanding that
*argument before reachingcat.catwill not see*, it will see the names of the files that match*(which is every file).
Character device example
ls - l 'tty'
c-...
Block device example
ls -l /dev/sda
b-..
Socket device example
nc -lU sock
ls -l
s-..
- This is used for networking, we will come back to these later
struct dirent (man 3 readdir)
struct dirent {
ino_t d_ino; // file # of entry
uint16_t d_reclen; // len of rec
uint8_t d_type; // file type
uint8_t d_namlen; // len of d_name
char d name[255 + 1];
};
- A directory consists of records (directory entries), each of which contains information about a file and a pointer to the file itself.
- Directory entries may refer to other directories as well as plain files; such nested directories are referred to as sub-directories.
- A hierarchy of directories and files is formed in this manner and is called a file system (or referred to as a file system tree).
Notes:
- The two most important entries here are first the
ino_t d_ino; - For every object in the filesystem, there is a unique object that the filesystem gives it, that is called the inode number.
- If you have limits in addressing inode numbers, there may be chunks of your disk that may be unusable, so it is important for filesystem designers to be careful about this.
- If you have a file, that single file can occupy different sectors randomly distributed all over the platter of your disk.
- Thanks to the inode array (where there will be one inode that corresponds this file) you will be able to read this file like if it was all in a linear space
- A file has a name, but an inode is really how the filesystem knows which file it is.
ls example:
ls -li
- The
iswitch gives the inode number of the listed files - With this inode you are able to see the exact layout of this file (everything about this file is handled to you)
- UNIX does not care about names!
- Really the file names are to there to help the user navigate through them, but UNIX does not care
- In UNIX what happens is that you build a namespace for yourself
Lets say you have:
/
foo/
fib
fab
- Since
foo/is a directory, it will have two entries, one iffiband one isfab, each with a unique inode - All you are doing is open the inode
- You are building a separate namespace that maps names to inodes. Your filesytem is only storing inode numbers and the content of these files. Your namespace is another mechanism
Common operations (system calls)
Basic Unix I/O operations (system calls)
- Opening and closing files
open()andclose()
- Reading and writing a file
read()andwrite()
- Current file position
- The kernel maintains a file position (initially
) for each open file (aka file pointer) - Indicates next (byte) offset in file to read from or write to
- Reading and writing automatically advance the file pointer
lseek()can be used to change file position upon request- Instead of the default which is tied to read and write operations
- The kernel maintains a file position (initially
/CSCE-313/Lecture/Visual%20Aids/image-18.png)
Opening Files
Opening a file informs the kernel that we are ready to access that file.
int fd = open("/etc/hosts", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
- This code returns an identifying integer file descriptor
fd == -1indicates that an error occurred, and errno is set- The kernel keeps track of all information about the open file, for e.g., current file position, permissions etc.
Notes:
- What is the difference of
openandfopen?- open is a system call, just as read/write
- What this is saying:
- If open returns -1 then it is going to set this variable
fd == -1
- If open returns -1 then it is going to set this variable
Each process created by a Unix shell [generally] begins with three open file descriptors associated with the terminal:
- 0: standard input
- 1: standard output
- 2: standard error
On success, open returns the smallest available fd value, e.g., in the picture below
int fd = open("/etc/hosts", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
Closing Files
Closing a file informs the kernel that we are finished accessing that file
if((retval = close(fd)) < 0) {
perror("close");
exit(1);
}
The kernel responds by
- (possibly) freeing some data structures
- restoring the descriptor to the pool of available descriptors
When a process terminates for any reason, the kernel performs the equivalent of close on all open files
Notes:
- Closes the filesystem and decrements the reference file
- If a process should terminate without closing, then the terminal does the equivalent
Creating a File
So far, we have only opened files for reading. The file must have existed beforehand. To create a file, we call open() with a flag set to O_CREAT and mode specifying appropriate permissions.
int open(const char *pathname, int flags, mode_t mode)
Consult the man page for more details. Here are some common use cases.
- We will commonly use one of these 3 flags:
O_RDONLY,O_WRONLY, orO_RDWRfor read, write or both, respectively. - There are other sub-flags that can be bitwise OR-ed with these, for e.g.,
O_CREAT. - When opening a file for reading, no need to provide the last argument mode, because you are not creating a file.
- However, when creating one, you must set the permissions of a file, which is next.
Notes:
- When you want to create a file you give it a class name, then you say: provide the option
O_...- It is a prefix to help you remember that these options are to open
open()cannot create a directory.- When you create a file you need to specify what permission bits it should bee created with.
- You want to make sure that when that file is created, there is no race condition (the file was created and it was readable by someone that should not be able to)
- The porpuse of this argument is to specify what mode the file should be created with to avoid those kind of stuff
Files on Disk
/CSCE-313/Lecture/Visual%20Aids/Pasted%20image%2020260223140237.png)
/CSCE-313/Lecture/Visual%20Aids/Pasted%20image%2020260223140307.png)
-
Green squares are inotes
-
blue and red are data buffers
-
Both /foo/a & /foo/b can refer to the same inode!
-
On disk, the green blobs represent inodes
-
The red and blue blobs represent data blocks for the two inodes
-
# of links for inode 63457 is 2!
Notes:
- You basically mount (attach) to your root (at some point in your namespace)
- foo is a directory
- Nothing more than a mapping between names that are in a directory and the inode number
- inode is a block of metadata that completely describes a file
- Will contain a complete sequence of disk blocks that constitutes to the blocks that the file occupies
- It will help sequence all the blocks in your file
- Some of this metadata includes permissions and ownsership of file
- It is entirely possible to have two different names in your filesystem that are referring to the same file
- a -> inode: 63457
- b -> inode: 63457
- This is the proof that this is just a constructive namespace, and you are the one who constructs it.
- Sample question: from this namespace how many names are pointing to the same file?
- These files could be anywhere as long as it is pointing to the same inode
- Whether you open name a or name b you will read exactly the same bytes on the disk.
- This is also known as a hard link
- Basically everything is hard link, but you can have two hard links that point to the same inode
- Linux Links
- Symlinks are objects in the filesystem, they have their own inode
- Symlinks can map across partitions
The stat structure
/CSCE-313/Lecture/Visual%20Aids/Pasted%20image%2020260223141159.png)
Notes:
- You need to use a special system call to get the data from an inode
- Most often the number of links is just 1, but as we saw above, we can have two names point at the same file
__uid_t st_uidis saying "which user owns the file?"__gid_t st_guidis saying "to which group does this file belong?"
Making a link of a file
ln mybomb mybomb2
- Now
mybomb2is referencing to the exact same object asmybomb - The number of links to a file will increase, and this link count will show on both
mybombandmybomb2file objects.
> ls -li
total 16
55675409 -rw-r--r-- 1 macc staff 582 Jan 21 16:55 2
55674416 -rw-r--r-- 1 macc staff 582 Jan 21 16:55 file
64037704 -rw-r--r-- 2 macc staff 0 Feb 23 14:15 mybomb
64037704 -rw-r--r-- 2 macc staff 0 Feb 23 14:15 mybomb2
- Note the number of links of the files with the same inode went up to 2
Look at your user:
> id
uid=501(macc) gid=20(staff) groups=20(staff),101(access_bpf),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae),701(com.apple.sharepoint.group.1)
- Users have numbers, the name is just a convenience
- This user can do r/w/x, which one of these
- Similarly you have a group ID, in addition to being a member of this primary group, we are also a member of these other groups.
- How are groups created?
- Not much more than a description in such file
- Go to
/etc/groupand see it yourself
st_mode bits
/CSCE-313/Lecture/Visual%20Aids/Pasted%20image%2020260223142053.png)
-
owner: perms if caller’s uid = file owner uid
-
group: perms if file’s group ∈
-
other: else
-
d → inode corresponds to a directory
-
c → inode corresponds to a char device
-
f → inode corresponds to a fifo
-
u+ → setuid bit-effective uid change on execution
-
g+ → setgid bit-effective gid change on execution
-
t → sticky bit-for directories
Notes:
- If any one of these groups matches this group, and you are not the own, then these 3 bits will apply
- If not, then these other 3 bits would apply
- There are some more bits in the
st_modeat the start:- If F is 1, it means it is a file, if d is 1, then it is a directory, since you can have both of these sets, then it means something else (i.e. a socket or something) if these are all 0, then either you are a character file (c), a fifo (f), or other stuff.
- The very strict order in which permissions are checked are
- First owner
- Then group
- Then other
- If the current user does not have any permission after all of these categories, then it will not be granted access to this file
- If you execute a program which has the set-uid bit set, then when you execute it, you become the owner of that program.
- Effective user id becomes 'owner'
- If you execute a program with the set-guid bit set, then when you execute it, your group becomes the group owner of that program
Example:
> ll
total 16
-rw-r--r-- 1 macc staff 582B Jan 21 16:55 2
-rw-r--r-- 1 macc staff 582B Jan 21 16:55 file
-rw-r--r-- 2 macc staff 0B Feb 23 14:15 mybomb
-rw-r--r-- 2 macc staff 0B Feb 23 14:15 mybomb2
drwxr-xr-x 6 macc staff 192B Feb 17 15:14 pa1-report
- You can clearly see the permission bits there
- You can see the 9 permission bits there
- first three assign permissions to owner
- second three assign permissions to group
- last three assign permissions to other
Example:
> stat -x mybomb
File: "mybomb"
Size: 0 FileType: Regular File
Mode: (0644/-rw-r--r--) Uid: ( 501/ macc) Gid: ( 20/ staff)
Device: 1,16 Inode: 64037704 Links: 2
Access: Mon Feb 23 14:15:25 2026
Modify: Mon Feb 23 14:15:25 2026
Change: Mon Feb 23 14:15:31 2026
Birth: Mon Feb 23 14:15:25 2026
- Gives information about the file
- Note the permission bits can also be represented in octal
- It is a scaled summation of binary bits represented in octal
- The
-xoption is to display in verbose mode
Creating a File
int open(const char *pathname, int flags, mode_t mode);
The following two items specifies permissions of a file:
| Types of Access | Types of Users |
|---|---|
| - Read (R) | - User/Owner (U) |
| - Write (W) | - Group (G) |
| - Execute (X) | - Others (O) |
- For each user, there is a 3-digit octet RWX that determines what level of permissions the user has.
- For instance, if the User/Owner has
, and X permission, he gets 7 (=111 in binary) - If the group has only
and permissions, everybody in that group gets 6 (=110 in binary), and no permission for others would give 0 . Together, the permission for the file should be: 760
Notes
- Here you are provideing basically the 9 permission bits
Creating a File
int open(const char *pathname, int flags, mode_t mode);
To create a file only for yourself. (umask bits cleared.)
int main() {
int fd = open("test.txt", O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
if(fd < 0) {
perror("Cannot create file "); exit(0);
}
cout << "File created successfully" << endl;
}
prompt> ls -l
total 36
-rwxrwxr-x 1 osboxes osboxes 17424 Sep 7 15:23 a.out
-rw-rw-r-- 1 osboxes osboxes 256 Sep 214:21 exec1.cpp
-rw-rw-r-- 1 osboxes osboxes 313 Sep 715:23 fileio1.cpp
-rw-rw-r-- 1 osboxes osboxes 288 Sep 123:08 fork1.cpp
-rw-rw-r-- 1 osboxes osboxes 496 Sep 214:01 fork2.cpp
-rwx----1 osboxes osboxes 0 Sep 715:23 test.txt
Note:
- If the file is already created, please truncated (erase it to 0)
- If the file does not exist, then create it.
File mode bits (man 7 inode)
/CSCE-313/Lecture/Visual%20Aids/Pasted%20image%2020260223143629.png)
- Symbolic constants for owner, group, and others
Creating a File (2)
int open(const char *pathname, int flags, mode_t mode);
However, keep in mind that you cannot just directly put 700 for permission because 700 in decimal is not the same as 700 in octet. You can write 0700 though, because that represents octal number.
int main () {
int fd = open ("test.txt", O_WRONLY|O_CREAT|O_TRUNC,700); // this is incorrect
if (fd < 0) {
perror ("Cannot create file "); exit(0);
}
cout << "File created successfully" << endl;
}
prompt> ls -l
total 36
-rwxrwxr-x 1 osboxes osboxes 17424 Sep 7 15:22 a.out
-rw-rw-r-- 1 osboxes osboxes 256 Sep 214:21 exec1.cpp
-rw-rw-r-- 1 osboxes osboxes 309 Sep 7 15:22 fileio1.cpp
-rw-rw-r-- 1 osboxes osboxes 288 Sep 123:08 fork1.cpp
-rw-rw-r-- 1 osboxes osboxes 496 Sep 2 14:01 fork2.cpp
--w-rwxr-- 1 osboxes osboxes 0 Sep 715:22 test.txt
- Note the file is created with
--w-rwxr--permissions
Notes:
- If you were opening it for writing, you will still overwrite that file
- You start with a truncated file and then you write to it!
- This will be on the exam!
Interesting questions from last class: See filesystems.html
- Symlinks and double access check?
- x without r
Mount a remote filesystem:
ls gcloud
- Mount filesystem on my gcp VM
- Use the
sshfscommand: Mount remote directory locally using sshfs - Depending on where you mount them is where you are building a different namespace
Steps:
-
First it enumerates the current directory
- All its saying is: "if you want to find the metadata for this object: go to this inode"
- In this example we retrieve the inode for
/homels -l / bat
-
Enumerate the next component of your file name
- Find the next inode that you want
ls -l /home - And so on
- Find the next inode that you want
The bit
- On files, it means that you can read the file.
- But you need
on the dir.
- But you need
- On directories, it means you can list files in the dir.
- But you need
to get the metadata for a file.
- But you need
~ ls -ld tmp
dr--r-xr-x 27 sandeep.kumar staff 864 Feb 24 18:50 tmp
~ /bin/ls tmp
[files...]
~ ls tmp/gforms.txt
Is: tmp/gforms.txt: Permission denied
Notes:
- If a file has the
bit, it means the caller can read the file - In unix it matters what the permissions on the container there are
- You need execute permission on the directory in order to be able to read the file
- Interpret:
- The
xbit on the directory allows you to search files by name and allows you to access the inode of that file.
- The
- Example:
- If a directory has the r bit set, it means you can enumerate the contents of that directory (plain
lswill work) - But if you do not have the
xbit on the directory, then you cannot get to the inode sols -lwon't work, because it needs the inode of all files - In order to be able to do everything you need to get to the inode.
- If a directory has the r bit set, it means you can enumerate the contents of that directory (plain
The bit
- On files, it means that you can write to the file.
- To delete a file, you need
on both the file and its containing dir.
~ ls -ld tmp
dr-xr-xr-x 26 sandeep.kumar staff 832 Feb 24 20:45 tmp
~ ls -l tmp/foo.gpg
-rw-r--r--@ 1 sandeep.kumar staff 99 Nov 25 18:28 tmp/foo.gpg
~ rm tmp/foo.gpg
rm: tmp/foo.gpg: Permission denied
Notes:
- Remember that this is not enough, you need to be able to get to that inode so you exactly need the
xbit on the directory of the file you want to write to - Note you need write permission in order to delete a file
The bit
- On files, it means that you can execute it with exec.
- On directories, it means you can find a file by name.
~ ls -ld tmp
d--xr-xr-x 27 sandeep.kumar staff 864 Feb 24 18:50 tmp
~ ls tmp
Failed to read cwd.
~ ls tmp/gforms.txt
tmp/gforms.txt
~ vi tmp/gforms.txt
works
- Need
on dir to enumerate. - On directories, it means you can find a file by name.
Notes:
- Means that you can actually give it to the
execsystem call and launch it as an executable. - If the first two bytes of this file start with "!" then they will read the rest of the argument as the program to run
- The
xbit means that you can find a file by name and get its inode - If you don't have the read permission but have the x permission, you can actually read and write to that file!
- Many folks will use this as a mean to creating passwords
- Make the directory non-readable unless you happen to know the precise name of the file
Example:
bat fixpath
- See that the first few bytes of this file are of this peculiar style:
#!/bin/sh- If
execfinds that on the first line, then it will give the current filename as an argument ofexec
- If
The open system call (Chapter 3 of APUE)
#include <fcntl.h>
int open(const char *pathname, int oflag, ... /* mode_t mode */);
oflag must be one (and only one) of:
- O_RDONLY - open for reading only
- O_WRONLY - open for writing only
- O_RDWR - open for reading and writing
and may be OR'd with any of these:
- O_APPEND - append on each write
- O_CREAT - create the file if it doesn't exist; requires mode argument
- O_EXCL - error if O_CREAT and file already exists. (atomic)
- O_TRUNC - truncate size to 0
- O_NONBLOCK - do not block on open or for data to become available
- O_SYNC - wait for physical I/O to complete
Notes:
- You can give arguments to open a file just for reading/writing or both
- You can OR'd one of these flags with the RW flag to say how you want to modify the open (i.e. APPEND - writes to the end of the file)
The close system call
#include <unistd.h>
int close(int fd); // returns: 0 if OK , -1 on error
- File descriptors not explicitly closed are closed by the kernel when the process terminates.
- To avoid leaking file descriptors, always close(2) them within the same scope.
Notes:
- When you close a file descriptor you are removing your reference count to that file
- Then that entry is garbage
- Closing is important because if you were the only guy pointing, then iy would have its side effects?
- It is generally a good practice to close
- The notion of a lexical scope:
- They will want you to close things in the same scope
- You can match then your open and your classes and have a better confidence that you are not leaking anything
The read system call
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t num);
- Returns number of bytes read; returns 0 on EOF, -1 on error.
- read begins reading at the current offset, and increments the offset by the number of bytes actually read.
- There can be several cases where read returns fewer than the number of bytes requested. For example,
- EOF reached before requested number of bytes have been read
- reading from a network, buffering can cause delays in arrival of data
- record-oriented devices (magtape) may return data one record at a time
- interruption by a signal
Reading Files
Reading a file copies bytes from the current file position to memory, and then updates file position.
char buf[512];
int fd = open(filename, O_RDONLY);
int nbytes;
/* Then read upto 512 bytes from file fd */
if ((nbytes = read(fd, buf, sizeof(buf))) < 0) {
perror("read");
exit(1);
}
Returns number of bytes read from file fd into buf
- Return type
ssize_tis signed integer nbytes < 0indicates that an error occurred- Short counts (
nbytes < sizeof(buf)) are possible and aren't errors- For e.g., 0 is returned when there are no more bytes to be read.
- A value < nbytes may also be returned when reading from terminal where user supplied less than nbytes.
Example:
- You want to read 512 bytes
- The semantics of UNIX does not guarantee the number of bytes you will get
- This problem is so perversive for Networking
- You will do a frame layer before you can actually look into the thing?
- You read and read until you accumulate enough bytes until you can frame it: somehow figuring out the message boundaries
- Takeaway: the semantics of read does not guarantee actually reading the bytes you ask for
- It will almost always be the case if you are reading stuff from a socket
- This is called short reads!
Reading files: example
What's the output of the following program that reads a file with contents "foobar"
char buf[2];
int fd = open(filename, O_RDONLY);
int nbytes = read(fd, buf, sizeof(buf)); // buf = ?
nbytes = read(fd, buf, sizeof(buf)); // buf = ?
Notes:
- File that contains 6 bytes
- What do you get in this array
buf[]? - You ask to read 2 bytes so you will do
- "fo"
- "ob"
- If you change this to
*buf[2]you will end up reading 8 bytes because that is the size of a pointer?
The write system call
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t num);
- Returns number of bytes written if OK; -1 on error.
- write returns the number of bytes written
- For regular files, write begins writing at the current offset
- Unless O_APPEND has been specified, in which case the offset is first set to the end of the file.
- After the write, the offset is adjusted by the number of bytes actually written.
Notes:
- Same as read
- You will write until the appropriate number of bytes until you accumulate all your writes to add up to
- When you are writing to a regular file there is a notion of current offset, you will write to that offset.
Writing files
Writing file copies bytes from memory to the current file position, and then updates current file position.
char buf[512];
int nbytes; /* number of bytes written*/
int fd = open(filename, O_WRONLY|O_CREAT, 0700);
/* Then write up to 512 bytes from buf to file fd */
if ((nbytes = write(fd, buf, sizeof(buf)) < 0) {
perror("write");
exit(1);
}
Returns number of bytes written from buf to file fd
nbytes < 0indicates that an error occurred- As with reads, short counts are possible and are not errors!
Writing files: example
What would be the content of the file after executing the following program?
char buf[] = {'a', 'b'}; // what if char *buf = “ab”
int fd; // file descriptor
/* Open the file fd ... */
/* Then write up to 2 bytes from buf to file fd */
write(fd, buf, sizeof(buf)); //file = ?
write(fd, buf, sizeof(buf)); //file = ?
Final file content:
abab
- Note, it would be a different story if we had defined
char *buf = "ab"because in that casebufwill be a pointer of size 8 and the string is only 3 bytes (a,b,\0)- So
write(fd, buf, sizeof(buf))will try to write:- the 3 valid bytes
- plus extra garbage bytes from memory after the string literal
a b \0 garbage garbage garbage garbage garbagea b \0 garbage garbage garbage garbage garbage
- So
File Representation: Three Tables
Descriptor Table
- Each process has its own separate descriptor table (only accessible directly by the kernel)
- Entries are indexed by the process's open file descriptors
- Each entry points to a File Table entry
File Table
- Shared by all processes.
- Each entry consists of
- File Cursor
- Reference count: # of descriptor entries from all processes that point to it
- ptr to the v-node table entry, along with status flags
v-node Table
- Each entry ( 1 per file) contains information about the file meta data (e.g., access/modification date, permissions)
How the Unix kernel represents open files?
- Two descriptors referencing two distinct open disk files.
- Descriptor 1 (stdout) points to terminal, and descriptor 4 points to an open disk file.
/CSCE-313/Lecture/Visual%20Aids/Pasted%20image%2020260227141630.png)
Notes:
- When you do a fork, that cursor is not duplicated, it is the same cursor that the child and parent share
- When the parent or child read their child/parent pointer will then change
File Sharing
Two distinct DTs sharing the same disk file through two distinct open FT entries
- E.g., calling
opentwice with the samefilenameargument - Every
opencreates a new DT and a new FT entry
/CSCE-313/Lecture/Visual%20Aids/image-19.png)