Linux/ Open directory as a file - c

I've been reading Brian Kernighan and Dennis Ritchie - The C Programming Language and chapter 8.6 is about directory listing under UNIX OS. They say that everything and even directory is a file. This means that I should be able to open directory as a file? I've tried it using stdio functions and it didn't work. Now, I'm trying it with UNIX system functions. Of course, I'm not using UNIX, I'm using Ubuntu linux. Here is my code:
#include <syscall.h>
#include <fcntl.h>
int main(int argn, char* argv[]) {
int fd;
if (argn!=1) fd=open(argv[1],O_RDONLY,0);
else fd=open(".",O_RDONLY,0);
if (fd==-1) return -1;
char buf[1024];
int n;
while ((n=read(fd,buf,1024))>0)
write(1,buf,n);
close (fd);
return 0;
}
This writes nothing even when argn is 1 (no parameters) and I'm trying to read current directory.
Any ideas/explanations? :)

Files are also called regular files to distinguish them from special files.
Directory or not a regular file. The most common special file is the directory. The layout of a directory file is defined by the filesystem used.
So use opendir to open diretory.

Nachiket's answer is correct (as indeed is sujin) but they don't clear up the mystery as to why open works and not read. Out of curiosity I made some changes to the given code to find out exactly what was going on.
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char* argv[]) {
int fd = -1;
if (argc!=1) fd=open(argv[1],O_RDONLY,0);
else fd=open(".",O_RDONLY,0);
if (fd < 0){
perror("file open");
printf("error on open = %d", errno);
return -1;
}
printf("file descriptor is %d\n", fd);
char buf[1024];
int n;
if ((n=read(fd,buf,1024))>0){
write(1,buf,n);
}
else {
printf("n = %d\n", n);
if (n < 0) {
printf("read failure %d\n", errno);
perror("cannot read");
}
}
close (fd);
return 0;
}
The result of compiling and running this:
file descriptor is 3
n = -1
read failure 21
cannot read: Is a directory
That settles it, though I'd have expected open to fail, since the correct system function for opening directories is opendir().

Though everything in unix is a file (directory also) but still filetype is concept is present in unix and applicable to all files.
there are file types like regular file,directory etc and certain operations and functions are allowed/present for every file type.
In your case readdir is applicable for reading contents of directory.

If you want to see the files in a directory you have to use the opendir and readdir functions.

K&R were correct for the original UNIX. I remember doing it back when UNIX file systems had a 14 character length limit for filenames. The opendir(), readdir(), ... stuff happened about the time that longer file names became common (around 1990?)

Related

usage of tmpfile() in C

I don't understand why my script below seems to work without creating any files.
script.c:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]){
printf("P_tmpdir is '%s'\n", P_tmpdir);
FILE *tmp = tmpfile();
if(tmp == NULL){
printf("Unable to create temp file");
exit(1);
}
else{
printf("Temporary file is created\n");
}
for(int i = 0; string[i] != '\0'){
fputc(string[i], tmp);
}
rewind(tmp);
while(!feof(tmp)){
putchar(fgetc(tmp));
}
sleep(3);
return(0);
}
The P_tmpdir variable returns me the "/tmp" directory although in the sleeping time no new file is created in it... can you help me or explain me plz ?
Quoting cppreference.com (emphasis mine):
On some implementations (e.g. Linux), this function actually creates, opens, and immediately deletes the file from the file system: as long as an open file descriptor to a deleted file is held by a program, the file exists, but since it was deleted, its name does not appear in any directory, so that no other process can open it.
The file does not have to be "visible" in the file system tree, as long as a process has a handle on it, the file continues to exist.
If you want a file that's visible in the file system tree you should use mkstemp.

How to tell if FILE* is referring to a directory?

I just discovered that a FILE* can not only refer to a regular file, but also to a directory. If the latter is the case, fread will fail with errno set to 21 (Is a directory).
Minimal repro can be tested here
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
int main() {
char const* sz = ".";
int fd = open(sz, O_RDONLY | O_NOFOLLOW); // all cleanup omitted for brevity
FILE* file = fdopen(fd, "rb");
// I would like to test in this line if it is a directory
char buffer[21];
int const n = fread(buffer, 1, 20, file);
if (0 < n) {
buffer[n] = 0;
printf(buffer);
} else {
printf("Error %d", errno); // 21 = Is a directory
}
}
What is the proper way to detect early that my FILE* is referring to directory without trying to read from it?
EDIT to repel the duplicate flags:
I want to test on the FILE*, not the filename. Testing on filename only and then opening it later is a race condition.
Assuming a POSIX-like environment, if you have just the file stream (FILE *fp), then you are probably reduced to using fileno() and fstat():
#include <sys/stat.h>
struct stat sb;
if (fstat(fileno(fp), &sb) != 0)
…oops…
if (S_ISDIR(sb.st_mode))
…it is a directory…
else
…it is not a directory…
Assuming you are on a POSIX-based system, use stat() (if you wish to use the filename in sz before the call to open()) or fstat() (if you wish to use the descriptor fd after calling open()) to get a file status structure from the OS. The member of the structure named st_mode can be used with the POSIX API S_ISDIR(st_mode) to see if the file is a directory.
For more information, see: http://man7.org/linux/man-pages/man2/stat.2.html
Checking The fcntl.h man page:
header shall define the following symbolic constants as
file creation flags for use in the oflag value to open() and
openat(). The values shall be bitwise-distinct and shall be suitable
for use in #if preprocessing directives.
And the flag :
O_DIRECTORY Fail if not a directory.

Why are directory files not read using fread?

I was going through an example from Maurice Bach's Unix Book. He writes a simple copy program like mentioned below. However it fails when the inputfile is a directory file. I did stumble upon opendir and few other such API's - should I use that?
If a binary file can work with this, why is directory file considered different? In Unix, isn't everything abstracted as a file regardless of the way it is interpreted by the program.
Also how can I extend this program to support directory file and then create a mknod of that? I want to test this, suppose I am in /home/user1 and do a $./copy /home/user user-home-clone and mknod it to see how that directory will be different from home. I guess that the user-home-clone might not have a reference to itself, but all the other files in /home/user [ even though it would a file called user-home-clone would be there in /home/user ] since it was not there when we did the copy command?
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
char buffer[2048];
int copy(FILE *source, FILE *destination)
{
int count;
while ((count = fread(buffer, 1, sizeof buffer , source)) > 0)
{
fwrite(buffer, 1, count, destination);
}
return 0;
}
int main(int argc, char* argv[])
{
int status;
FILE *source;
FILE *destination;
if (argc != 3)
{
printf("%s takes exactly 3 arguments\n", argv[0]);
exit(1);
}
source = fopen(argv[1], "r");
if (source == NULL)
{
printf("%s can't be opened for reading\n", argv[1]);
exit(1);
}
destination = fopen(argv[2], "wb");
if (destination == NULL)
{
printf("%s can't be opened for writing\n", argv[2]);
exit(1);
}
if (copy(source, destination) == 0)
{
status = 0;
}
else
{
status = 1;
}
fclose(source);
fclose(destination);
exit(status);
}
I use Centos 6.5 Linux Ext4 Filesystem
In early versions of Unix, directory files could be read as binary files. However, when network and other types of virtual filesystems were added, this ability was removed, because different filesystems implement directories differently. While it would be technically possible for the driver to emulate a byte stream structure for these directories, it wasn't seen as a useful feature. Directories should be treated as opaque, abstract collections, and accessed using the directory-specific functions.

open a windows file directory for reading/writing in c

I'm trying to write the contents of a windows directory to a file using c. For example, if I had a directory of jpegs (i.e. a directory that contains multiple jpegs) and wanted to convert them to a .raw file, I have something like this:
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
typedef uint8_t BYTE;
#define BLOCK 512*sizeof(BYTE);
int main(void)
{
FILE * fd = fopen("C:\\jpegs", "r");
if (fd == NULL) {
fprintf(stderr, "Error opening device file.\n");
return EXIT_FAILURE;
}
int block = BLOCK;
FILE * fn = fopen("new.raw", "w+");
void * buff = malloc(block);
while(feof(fd) == 0) {
fread(buff,block,1,fd);
fwrite(buff,block,1,fn);
}
free(buff);
fclose(fd);
fclose(fn);
return 0;
}
The problem is I don't think windows directories are terminated with EOF. Does anyone have any ideas about how to solve this?
On Unix systems, although you can open a directory for reading, you can't really read from it unless you use the opendir(), readdir(), closedir() family of calls. You can't write to a directory on Unix; even superuser (root) can't do that. (The main reason for opening a directory, more usually with open() than fopen(), is so that you can use chdir() followed by fchdir() to get back to where you started, or use the various *at() functions, such as openat(), to reference the directory.)
On Windows, you'd at minimum need to use "rb" mode, but frankly, I'd not expect you to be able to do much with it. There are probably analogues to the Unix opendir() functions in the Windows API, and you should use those instead.

How can I use Linux's splice() function to copy a file to another file?

here's another question about splice(). I'm hoping to use it to copy files, and am trying to use two splice calls joined by a pipe like the example on splice's Wikipedia page. I wrote a simple test case which only tries to read the first 32K bytes from one file and write them to another:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv) {
int pipefd[2];
int result;
FILE *in_file;
FILE *out_file;
result = pipe(pipefd);
in_file = fopen(argv[1], "rb");
out_file = fopen(argv[2], "wb");
result = splice(fileno(in_file), 0, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
printf("%d\n", result);
result = splice(pipefd[0], NULL, fileno(out_file), 0, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
printf("%d\n", result);
if (result == -1)
printf("%d - %s\n", errno, strerror(errno));
close(pipefd[0]);
close(pipefd[1]);
fclose(in_file);
fclose(out_file);
return 0;
}
When I run this, the input file seems to be read properly, but the second splice call fails with EINVAL. Anybody know what I'm doing wrong here?
Thanks!
From the splice manpage:
EINVAL Target file system doesn't support splicing; target file is
opened in append mode; neither of the descriptors refers to a
pipe; or offset given for non-seekable device.
We know one of the descriptors is a pipe, and the file's not open in append mode. We also know no offset is given (0 is equivalent to NULL - did you mean to pass in a pointer to a zero offset?), so that's not the problem. Therefore, the filesystem you're using doesn't support splicing to files.
What kind of file system(s) are you copying to/from?
Your example runs on my system when both files are on ext3 but fails when I use an external drive (I forget offhand if it is DOS or NTFS). My guess is that one or both of your files are on a file system that splice does not support.
The splice(2) system call is for copying between files and pipes and not between files, so it can not be used to copy between files, as has been pointed out by the other answers.
As of Linux 4.5 however a new copy_file_range(2) system call is available that can copy between files. In the case of NFS it can even cause server side copying.
The linked man page contains a full example program.

Resources