Monday, 18 June 2012

Session, Foreground processes, Background processes, and their interaction with Controlling Terminal.

For more info visit

Session: A session is a collection of process groups. In a session there can be only one foreground process group and more than one background process groups. The process which starts the session is known as session leader. A session leader can either be in a foreground process group or in a background process group.

Run the following command in your terminal to get the idea

ps -o pid,ppid,pgid,sid,tpgid,comm

The output will be similar as following

  PID     PPID   PGID      SID     TPGID  COMMAND 17335  17334  17335  17335  17495    bash 17495  17335  17495  17335  17495    ps

The column SID gives us the information about the session leader for the session. The SID will always be the process group id of the session leader, or we can also say that it will be same as id of session leader. Why?

Because whenever a new session is started, there is only a single process in the session, which is also known as session leader. This session leader is the process which starts the session.

In the above case, the session leader process is shell. We can also start our own sessions. Sessions are not started from some new process, but they are started from the currently existing process. The existing process is in some session before starting a new session. So, whenever a new session is started, the process which starts the new session is transferred to a new process group with group id same as its process id, means that the session starter process becomes the group leader of the new group. This new session contains only a single process, which is the starter process, and a single group in it.

As I said, a session leader can either be in foreground process group or background process group. In the above case, the shell, which is the session leader, is in background process group

Now, run the same command in background.

ps -o pid,ppid,pgid,sid,tpgid,comm &

The output will be

PID       PPID      PGID    SID     TPGID  COMMAND

17601  17600  17601   17601  17601     bash 17610  17601  17610   17601  17601      ps

Now, in this case, the shell is in foreground process group. How do we get to know, which is foreground process group and which is background process group?

Here comes the concept of Controlling terminal

A controlling terminal is the terminal device which is connected to a session. A session can have only one  terminal controller. The session leader, which makes the connection with the controlling process, is called Controlling Process. The Controlling terminal can only interact with the foreground process group.

The TPGID visible in the above example is Terminal process Group id, which is nothing, but the process group id of the foreground group process leader, which in second case is the shell.

The interrupts from the terminal such as ^C, ^D, etc are only going to effect the processes in the foreground process group.

Any Background process group can always be brought Foreground, provided the Background process group is in the same session as Foreground process group.

Now, let's see a simple example of toggling between Foreground process group and Background process group

#include<stdio.h> #include<stdlib.h> int main() { int pid; pid=fork(); if(pid==0) { setpgid(getpid(),getpid()); sleep(2); printf("\nin child process"); printf("\nchildpid %d",getpid()); printf("\nchildgrpid %d\n",getpgrp()); printf("\nchild done"); printf("\nchanged fpgid to child process%d\n",tcgetpgrp(1)); } else { printf("\ninitial fpgid %d\n",tcgetpgrp(1)); printf("\nin parent process"); printf("\nparentpid %d",getpid()); printf("\nparentgrpid %d\n",getpgrp()); tcsetpgrp(1,pid); wait(); printf("\nparent done"); printf("\nchanged fpgid to parent process%d\n",tcgetpgrp(1)); } exit(0);

Compile and run the above program The output will be -

initial fpgid 17737

in parent process parentpid 17737 parentgrpid 17737

in child process childpid 17738 childgrpid 17738

child done changed fpgid to child process17738

[1]+  Stopped                 ./a.out

Now, let's analyse the program from beginning.

We fork() a child process and change its group id to its pid. Why? This is just to transfer the child process in some other group, as, after forking it would have inherited the group of its parent. We make it sleep for 2 secs, in order to run the parent process first.

In parent process, the parent's pid, pgid, and Foreground process group id is printed. Initially, the fpgid is same as parentgrpid. After printing all this, we change the fpgid to childgrpid or childpid. Parent process waits for child process to terminate.

Child process starts after 2 secs. It prints childpid, childgrpid and changed fpgid which now is same as childgrpid. This means, now child process group is the foreground process group. Child process prints "child done" and terminates.

Now what is this "[1]+  Stopped                 ./a.out".

Looks like some process has been stopped in the background. After seeing, we get to know that parent process has been stopped.

Why was it stopped?

It was stopped because it received signal SIGTTOU while trying to print the last two statements on the terminal. This terminal sends this signal to any background process group which tries to print something on terminal.

Since the parent process is not allowed to continue, the child process has also become ZOMBIE. Though the parent process has received the exit status of child process, it couldn't delete its details from process structure table.

Now we will start this stopped parent process by bringing it in foreground.

fg %1

The output will be

parent done changed fpgid to parent process17737

One more thing, just now we changed the fpgid to child process's group id. How did it revert back to parent process's group id.

It's simple, because when we brought the job with job id 1 to Foreground, all the processes of the group came to foreground.

Note: Make sure you run the following command, before executing above program.

stty tostop

This command stop the background jobs that try to write to the terminal.

Sunday, 10 June 2012

Session, Foreground processes, Background processes, and their interaction with Controlling Terminal.

For more info visit

Session: A session is a collection of process groups. In a session there can be only one foreground process group and more than one background process groups. The process which starts the session is known as session leader. A session leader can either be in a foreground process group or in a background process group.

Run the following command in your terminal to get the idea

ps -o pid,ppid,pgid,sid,tpgid,comm

The output will be similar as following

  PID     PPID   PGID      SID     TPGID  COMMAND 17335  17334  17335  17335  17495    bash 17495  17335  17495  17335  17495    ps

The column SID gives us the information about the session leader for the session. The SID will always be the process group id of the session leader, or we can also say that it will be same as id of session leader. Why?

Because whenever a new session is started, there is only a single process in the session, which is also known as session leader. This session leader is the process which starts the session.

In the above case, the session leader process is shell. We can also start our own sessions. Sessions are not started from some new process, but they are started from the currently existing process. The existing process is in some session before starting a new session. So, whenever a new session is started, the process which starts the new session is transferred to a new process group with group id same as its process id, means that the session starter process becomes the group leader of the new group. This new session contains only a single process, which is the starter process, and a single group in it.

As I said, a session leader can either be in foreground process group or background process group. In the above case, the shell, which is the session leader, is in background process group

Now, run the same command in background.

ps -o pid,ppid,pgid,sid,tpgid,comm &

The output will be

PID       PPID      PGID    SID     TPGID  COMMAND

17601  17600  17601   17601  17601     bash 17610  17601  17610   17601  17601      ps

Now, in this case, the shell is in foreground process group. How do we get to know, which is foreground process group and which is background process group?

Here comes the concept of Controlling terminal

A controlling terminal is the terminal device which is connected to a session. A session can have only one  terminal controller. The session leader, which makes the connection with the controlling process, is called Controlling Process. The Controlling terminal can only interact with the foreground process group.

The TPGID visible in the above example is Terminal process Group id, which is nothing, but the process group id of the foreground group process leader, which in second case is the shell.

The interrupts from the terminal such as ^C, ^D, etc are only going to effect the processes in the foreground process group.

Any Background process group can always be brought Foreground, provided the Background process group is in the same session as Foreground process group.

Now, let's see a simple example of toggling between Foreground process group and Background process group

#include<stdio.h> #include<stdlib.h> int main() { int pid; pid=fork(); if(pid==0) { setpgid(getpid(),getpid()); sleep(2); printf("\nin child process"); printf("\nchildpid %d",getpid()); printf("\nchildgrpid %d\n",getpgrp()); printf("\nchild done"); printf("\nchanged fpgid to child process%d\n",tcgetpgrp(1)); } else { printf("\ninitial fpgid %d\n",tcgetpgrp(1)); printf("\nin parent process"); printf("\nparentpid %d",getpid()); printf("\nparentgrpid %d\n",getpgrp()); tcsetpgrp(1,pid); wait(); printf("\nparent done"); printf("\nchanged fpgid to parent process%d\n",tcgetpgrp(1)); } exit(0);

Compile and run the above program The output will be -

initial fpgid 17737

in parent process parentpid 17737 parentgrpid 17737

in child process childpid 17738 childgrpid 17738

child done changed fpgid to child process17738

[1]+  Stopped                 ./a.out

Now, let's analyse the program from beginning.

We fork() a child process and change its group id to its pid. Why? This is just to transfer the child process in some other group, as, after forking it would have inherited the group of its parent. We make it sleep for 2 secs, in order to run the parent process first.

In parent process, the parent's pid, pgid, and Foreground process group id is printed. Initially, the fpgid is same as parentgrpid. After printing all this, we change the fpgid to childgrpid or childpid. Parent process waits for child process to terminate.

Child process starts after 2 secs. It prints childpid, childgrpid and changed fpgid which now is same as childgrpid. This means, now child process group is the foreground process group. Child process prints "child done" and terminates.

Now what is this "[1]+  Stopped                 ./a.out".

Looks like some process has been stopped in the background. After seeing, we get to know that parent process has been stopped.

Why was it stopped?

It was stopped because it received signal SIGTOUT while trying to print the last two statements on the terminal. This terminal sends this signal to any background process group which tries to print something on terminal.

Since the parent process is not allowed to continue, the child process has also become ZOMBIE. Though the parent process has received the exit status of child process, it couldn't delete its details from process structure table.

Now we will start this stopped parent process by bringing it in foreground.

fg %1

The output will be

parent done changed fpgid to parent process17737

One more thing, just now we changed the fpgid to child process's group id. How did it revert back to parent process's group id.

It's simple, because when we brought the job with job id 1 to Foreground, all the processes of the group came to foreground.

Wednesday, 6 June 2012

What happens if we call System from a Set-User-Id Program?

For more info visitWhat is System function and why do we use it? System is a function provided by C library STDLIB, it is used to execute a command string from within a C program. We can understand it to be the substitute for fork() and exec(). Instead of forking a new child process and calling exec function to execute a new command, we can directly use System function to execute a new command. Also, System function has a lot of advantages other than just fork() and exec(). Features: i) It forks(), exec() and waits for the child process. ii) Performs error handling if fork() or exec() or waitpid() fails. iii) Doesn't handle signals. We can use system in our C program as follows system("ls -lrt"); Just a simple statement and we can execute all the commands, scripts and binary executables from our program, without using fork() and execute(). But, what happens when we try to use system function with Set-User-Id programs. Let's say there is a user, user1, who is the owner of two programs, prog1 and prog2. Now, suppose that user1 has set SUID bit for prog1, but no SUID bit has been set for prog2, i.e user1 wants that, whenever prog1 runs, it should run with the owner's permission, but prog2 should always run with the user's permission. Let's  write prog1.c #include<stdio.h> #include<stdlib.h> int main() { printf("\nreal user id %d\n",getuid()); printf("\neffective user id %d\n",geteuid()); system("prog2"); exit(0); } Now write prog2.c #include<stdio.h> #include<stdlib.h> int main() { printf("\nin prog2\n"); printf("real user id %d\n",getuid()); printf("effective user id %d\n",geteuid()); printf("exit prog2\n"); exit(0); } Compile prog2.c and prog1.c. Suppose we get two binary executables, prog1 and prog2. Set SUID bit for prog1. Now, log in as different user, let's say user2, set the directory of prog2 in PATH environment variable. Run prog1 as user2. The output shall be real user id 1197 effective user id 1196 in prog2 real user id 1197 effective user id 1196 exit prog2 Did you notice the hole in security. Even though prog2 was not SUID bit set, it was run with the file owner's permission. Always be careful while using System function. The above problem can be solved by adding an extra line of code in prog1. Modified prog1 which is secure, #include<stdio.h> #include<stdlib.h> int main() { printf("real user id %d\n",getuid()); printf("effective user id %d\n",geteuid()); setuid(getuid()); system("prog2"); exit(0); } Now compile and run the prog1 as user2. Don't forget to set the SUID bit for prog1. The output shall be real user id 1197 effective user id 1196 in prog2 real user id 1197 effective user id 1197 exit prog2 getuid() gives the real user id  and setuid(getuid()) sets the effective useri id, before calling the system function, as real user id. So, when system() is called, real user id is passed to the exec() function used in the system function, and is set as the effective user id. Whenever a new child process is executed, the exec() copies the effective user id from the parent process. Infact, setuid() doesn't only set the effective user id of the process. It performs different actions in different situations. However, in this scenario, it only sets the effective user id of the process.

Thursday, 31 May 2012

The EXEC Family

For more info visitBefore getting into the family structure of EXEC, let's  first understand, what is exec. exec is a command of unix which overlays the currently running process area with the details of a new process, i.e. as soon as we run a new process using exec , the process area used by the running process in the memory is replaced by the new process. A child process is not created when exec is used. We all know that unix shell provides an environment for running  processes, but at the same time, this is also true that it itself is a process. Try running exec with any command or a script name from the shell prompt, you will notice that the shell shuts down abruptly. This is because the process area of the shell, which contains the details of currently running shell process, is replaced by a new process. The process doesn't return anything after successful execution of exec command, because the process itself is replaced by a new process in the memory. Normally, we use exec and its family in child processes, so that the parent process keeps on running smoothly. This was about exec command which we use directly in unix. Now we also have some library functions and a system call in exec family. Infact, exec command use these library functions and system call to get its work done.

EXEC FAMILY:

There are six members in exec family, out of which five are library functions and one is a system call. Ultimately, all the library functions use the system call for their needs. The five library functions are execl(char * pathname , const char *arg1.......const char *argn , (char *)0) execv(char *pathname , char *const arg[]) execlp(char *filename , const char *arg1.......const char *argn ,  (char *)0) execvp(char *filename , char *const arg[]) execle(char *pathname , const char *arg1.......const char *argn , (char *)0 , char * const env[]) execve(char *pathname , char *const arg[] , char * const env[]) Out of these six members, the last one, which is shown in bold, is a system call. We can use any of these members in our programs depending on our needs. There are some slight differences among the members. execl: The first member, which is execl, takes the full pathname of the script or command as its first argument, the arguments to be passed for that script or command as its subsequent arguments, and at last, it also accepts a null pointer which marks the end of arguments. The following program explains,  how execl can be used. Suppose we have a simple script myscript.sh #!/bin/ksh echo "Hello World" Now, we will write a simple c program to understand the usage of execl #include int main() { execl("/home/user1/buff/myscript.sh","myscript.sh",(char *)0); exit(0); } Compile and run this program. Output will be Hello World. execv: There is only a minor difference between execl and execv. After seeing the following example, you will get to know on your own. #include int main() { char *arg[]={"myscript.sh",(char *)0}; execv("/home/user1/buff/myscript.sh",arg); exit(0); } Did you notice the difference? In execv, we can pass the arguments for the script in an array, instead of passing them individually. execlp: It's same as execl with the only difference that we can pass the script name, instead of full pathname. The only thing is that, we will have to set the path of the directory in which the script to be run using exec is residing. execlp will automatically search the PATH environment for the full pathname of the script. Below is the example for execlp. #include int main() { execlp("myscript.sh","myscript.sh",(char *)0); exit(0); } execvp: It it same as execv with the only difference that it takes the full pathname of the script directly from PATH environment variable. Only replace the individual arguments given in the execlp function by an array argument. I am not giving the example for this function, as it is very simple. execle: The only difference between this function and all other functions is that it takes an extra environment variable in the parameters passed to it. Normally, when a child spawns, all the environment variables from parent are passed to the child. Sometimes, we may not want to pass all the environment variables, or we may only want to pass child specific environment variables. For doing this, we use execle function. Let's see an example for this function. #include int main() { char * env[]={"PATH=/home/user1/buff"}; execle("/home/user1/buff/myscript.sh","myscript.sh",(char *)0,env); exit(0); } Here we are going to do a slight change in myscript.sh to see the change due to env parameter. The changed script is given below. #!/bin/ksh echo $PATH echo "Hello World" The output will be /home/user1/buff Hello World The PATH variable for the child script contains only that value which we had passed in the env parameter. execve: This is the last and the most important member of the exec family. It's the only system call in the family. Every other member of the family uses this system call to get its work done. The syntax and working is same as execle, with the only difference that it takes arguments for the script or command to be run,  in an array.

Saturday, 26 May 2012

printf() anomaly after fork()...

For more info visitActually the title of this post is wrong, printf() doesn't show any deviation from its normal behaviour. Everything is fine with printf(), but then, after seeing a couple of examples given below, you will definitely say that something is wrong with printf(), until I explain you the reason behind this behaviour. Now let's see a couple of examples. 1)#include<stdio.h> int main() { int pid; printf("Hello World"); if((pid=fork())==0) { printf("\nHello, I am child\n"); } else if(pid>0) { sleep(1); printf("\nHello, I am Parent\n"); } exit(0); } I wrote sleep(1) in the program because I just wanted the output of child process to come before parent process, otherwise, there is no special significance of sleep(1) in this program. Compile and run this program, and you will find an interesting output. The output of this program will be OUTPUT Hello World Hello, I am child Hello World Hello, I am Parent Did you notice the anomaly in the output? "Hello World" is printed twice on terminal despite of the fact that we have only one print statement printing "Hello World" in our program, and that also before fork(). So, where did this second "Hello World" came from? Now, let's see, what actually happened? We know that printf() is line buffered when it is connected to the terminal ,and fully buffered when connected to any other device. The first printf() statement in the program puts its output in the output buffer connected to STDOUT stream, instead of directly printing it on STDOUT. This is because it has not encountered '\n' yet. So, a hidden output buffer is in existence in the address space of process. Now comes the interesting part, when forking takes place, a new child process is born. We all know that during the forking, complete address space of parent process is copied to child's address space. This hidden buffer connected to STDOUT stream also gets copied to child's address space. After understanding this, let's go with the flow of the program. i) "Hello World" is in the output buffer in the parent's address space after the first printf is executed. It hasn't got flushed to STDOUT stream because, '\n' is not encountered yet. ii) Fork() copies this output buffer with data "Hello World" to the child's address space.Now child also contains an output buffer with data "Hello World" connected to the same stream, i.e STDOUT. iii) The parent process sleeps for 1 sec. Child process executes the printf() statement inside the if { } condition. First '\n' is encountered, and "Hello World" from the output buffer of child is flushed out to STDOUT. After that, "Hello ,I am child" is also flushed to STDOUT. iv) Parent process wakes up after 1 sec and after finding first '\n' , it flushes "Hello world" to STDOUT. After this, it again flushes "Hello, I am Parent" to STDOUT. In the next example, we are redirecting the STDOUT stream of  both child and parent processes to a file. Let's do some slight changes in our program. 2)#include<stdio.h> int main() { int pid; printf("Hello World\n"); if((pid=fork())==0) { printf("Hello, I am child\n"); } else if(pid>0) { sleep(1); printf("Hello, I am Parent\n"); } exit(0); } Note the changes which we have made in our program. All the changes are made inside printf() statements. Run this program. The output will be OUTPUT Hello World Hello, I am child Hello, I am Parent which is absolutely normal, since I have already flushed my data before forking(). Now, run this program as follows ./a.out>temp We are redirecting the STDOUT stream of both the processes to a file, "temp". Open the file "temp "with cat temp. The content of the temp will be Hello World Hello, I am child Hello World Hello, I am Parent Again, there is a reason for this behaviour. When printf() is connected to a stream other than STDOUT then it is fully buffered. This means, even '\n' cannot flush the data. The data is flushed at the end, when both the processes terminate.

Saturday, 19 May 2012

Difference between Date, Timestamp, Timestamp with Time Zone and Timestamp with Local Time Zone

For more info, visitLet's  create a table to understand the difference between these four. The table script will be create table mytime(datetime1 DATE,datetime2 TIMESTAMP,datetime3 TIMESTAMP WITH TIME ZONE,datetime4 TIMESTAMP WITH LOCAL TIME ZONE); We have created a table with four columns with datatypes of DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE. Now, let's understand what their definitions have to say about them. DATE: Can store a date and the time resolved to seconds. TIMESTAMP: DATE and TIMESTAMP are almost same, with the only difference that TIMESTAMP is able to resolve to a billionth of a second, i.e. it has 9 decimal places of precision for a second. TIMESTAMP WITH TIME ZONE: Contains all the features of a TIMESTAMP and additionally, stores the TIME ZONE information with it. TIMESTAMP WITH LOCAL TIME ZONE: This is one of the tricky datatype. Though I am writing its definition, but I am sure it won't be much clear until we see an example. The definition is Its name has TIME ZONE in it, it doesn't actually stores the time zone. Whenever we store a date time with time zone information in this field, it converts the date time with time zone information into database time zone information, and stores it. After this, whenever any session fetches the information from this field, it first converts the information into the local session time zone and presents it. We have already created a table mytime. Now let's insert values in it. Our insert script will be insert into mytime values(to_date('20-may-2012 10:30:40','dd-Mon-yyyy hh:mi:ss'),TO_TIMESTAMP('20-may-2012 10:30:40.123456 AM','dd-Mon-yyyy hh:mi:ss.FF6 AM'),TO_TIMESTAMP_TZ('20-may-2012 10:30:40.123456 AM EST','dd-Mon-yyyy hh:mi:ss.FF6 AM TZR'),TO_TIMESTAMP_TZ('20-may-2012 10:30:40.123456 AM EST','dd-Mon-yyyy hh:mi:ss.FF6 AM TZR')); We are inserting values in four columns as shown above. Now, alter your session and set your time zone to -7.00 as shown below. ALTER session SET time_zone='-7:00'; Why did we change the session time zone? We will get to know in more detail after seeing the result of the following select query. select * from mytime; And the output is DATETIME1 -------------------- DATETIME2 --------------------------------------------------------------------------- DATETIME3 --------------------------------------------------------------------------- DATETIME4 --------------------------------------------------------------------------- 20-may-2012 10:30:40 20-MAY-12 10.30.40.123456 AM 20-MAY-12 10.30.40.123456 AM EST 20-MAY-12 07.30.40.123456 AM DATETIME1 reports the date and time in simple format. DATETIME2 reports the date and time with extra precision in seconds. DATETIME3 reports the date and time with time zone. DATETIME4 is the most interesting. It calculates and reports the date and time according to the local session timezone. NOTE: By default DATETIME1 column will not display the time information. You will have to alter your session as follows. Alter session set NLS_DATE_FORMAT='dd-mon-yyyy hh:mi:ss';

Sunday, 13 May 2012

Hole in the file.

For more info visitIf you would ask me this question, I would say, this hole is the only one of its kind which is not visible to a human, unless they really know file handling. Otherwise, a hole in anything is always visible, doesn't matter how much small it is, it always has a physical existence. Hole in the file doesn't mean that your file is perforated , i.e you opened a file, read some data, and then damn, your file is perforated so you couldn't read some data, and then again you started reading after the hole. There is nothing like loss of data due to a hole in the file. There is also no need for the hole to occupy any disk block. Although this statement is not totally true. In some cases holes may occupy some disk space. At the end of this post, I will let you know the scenario in which holes may occupy some disk space. Now how can a file with a hole is created. Given below is a simple program in c which will generate a file with a hole.   #include<sys/stat.h> #include<stdio.h> #include<fcntl.h> int main() { char databfhole[]="abcdefghij"; char dataafhole[]="klmnopqrst"; int fd; fd=creat("filewithhole",S_IRUSR|S_IWUSR); write(fd,databfhole,10); lseek(fd,20000,SEEK_CUR); write(fd,dataafhole,10); close(fd); return 0; } In the above code we are creating a file "filewithhole". S_IRUSR and S_IWUSR give read and write permission to the owner of file. After the file is created, file pointer will point at the beginning of the file. WRITE function writes a string of characters stored in the array databfhole. lseek is used to seek 20000 position past the current position of the file pointer. After that we again write some data, in array dataafhole, to the file Note: lseek doesn't write anything in the file, it just moves the position of current offset in the file table. Compile and run the program filewithhole is created. Now run the following command to see the contents of file   cat filewithhole And the output will be abcdefghijklmnopqrst But, when we check the size of the file it is 20020 bytes. So, what are these other bytes, if not visible characters? Let's check it with the use of od command. Run od -c filewithhole 0000000   a   b   c   d   e   f   g   h   i   j  \0  \0  \0  \0  \0  \0 0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0 * 0047040  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0   k   l   m   n   o   p 0047060   q   r   s   t We can see that all other hidden characters are null characters or 0. Depending on the file system and data block size, these holes may or may not occupy any area in disk. How? Suppose my data block size is 2K, i.e. 2048 bytes. I have a file with hole which has a  size of 8000 bytes. Now, let's say my first 1000 bytes are full of data, then there are holes after that, and then again I have last 1000 bytes of data. In this scenario my first data block will have 1000 bytes of data and 1048 bytes with value 0. So, these 1048 bytes hole would occupy some space in the disk. After this 2 data blocks of holes will be discarded i.e. they will not occupy any space on the disk, and then again starting from fouth data block i.e 6144 byte, holes will occupy some disk space till 6999. The last 1000 bytes will then be occupied by the data. So, we can see that only two data blocks were used to store this file.