My program for copying one file to another fails - c

I was trying to copy what is written in a file to a different file (with system calls) but my code seems not to work. I have first tried just printing with printf() the buffer but it also does not work. My guess is that I'm reading the file incorrectly.
#define BUF_SIZE 200
int main(int argc, char *argv[]){
int entrada,salida,leidos;
char buffer[BUF_SIZE];
entrada = open(argv[1],O_RDONLY);
salida = creat(argv[2], 0644);
while( (leidos = read(entrada,buffer,BUF_SIZE)) > 0 ){
write(salida,buffer,leidos);
}
close(salida);
close(entrada);
return 0;
}
What's wrong with my implementation?

I think you're missing the appropriate open flags on the output . Try:
salida = creat(argv[2], O_WRONLY | O_CREAT, 0644);
However, as comments suggest, you're probably getting errors indicated by the return values and/or the errno variable, which you are ignoring.
Also, I would avoid Castellano-specific variable names. Writing C/C++ requires knowing English anyway, so better stick to that for naming; otherwise - people who don't speak Castellano will have trouble understanding your code.
Finally - why are you doing it this way? There are much nicer C++-friendly, or even C-friendly, ways to copy a file - which would also be portable (your code isn't). See:
Copy a file in a sane, safe and efficient way

As #einpoklum stated, the main problem must be probably searched in the way you are opening your output file (flags and permissions). Overall, your code is far from implementing the minimum debugging verbosity and I think that a few code flow controls would help you a lot in detecting the real problem in your code:
#include <stdio.h>
#include <stdlib.h>
#ifndef BUF_SIZE
#define BUF_SIZE 200
#endif
int main(int argc, char *argv[])
{
int entrada = open(argv[1], O_RDONLY);
if (entrada == -1)
{
fprintf(stderr, "Error opening: %s\n", argv[1]);
exit(-1);
}
int openFlags = O_CREAT | O_WRONLY | O_TRUNC;
mode_t filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
int salida = open(argv[2], openFlags, filePerms);
if (salida == -1)
{
fprintf(stderr, "Error opening: %s\n", argv[2]);
exit(-1);
}
ssize_t numRead;
char buf[BUF_SIZE];
while ((numRead = read(entrada, buf, BUF_SIZE)) > 0)
{
if (write(salida, buf, numRead) != numRead)
{
fprintf(stderr, "Writing error!\n");
exit(-1);
}
}
if (numRead == -1)
{
fprintf(stderr, "Reading error!\n");
exit(-1);
}
if (close(entrada) == -1)
{
fprintf(stderr, "Input closing error!\n");
exit(-1);
}
if (close(salida) == -1)
{
fprintf(stderr, "Output closing error!\n");
exit(-1);
}
exit(0);
}
Many of the functions you use return values that can provide you an insight on that's going on. Use them.

Related

Concatenating files using read and write system calls

I wrote this simple code to concatenate two files. Why doesn't this work? Please help.
int main(int arg, char* argv[]){
int fd1, fd2;
char c;
fd1 = open(argv[1], O_APPEND);
fd2 = open(argv[2], O_RDONLY);
while(read(fd2, &c, sizeof(c))) {
write(fd1, &c, sizeof(c));
}
close(fd1);
close(fd2);
return 0;
}
As I noted in a comment:
You need to test the results of open() and write(). Your call with O_APPEND needs O_WRONLY too. And if you want it to create the file too, you need various other options too — O_CREAT; maybe O_TRUNC but probably not; maybe O_EXCL; maybe some others too — and you need a mode argument for the third argument (e.g. 0644 — owner can read or write, group and others can only read; or something more restrictive).
You also need to check that you have 2 file name arguments.
Fixing those issues leads to code similar to this:
int main(int arg, char* argv[]){
if (argc < 3)
{
fprintf(stderr, "Usage: %s out-file in-file\n", argv[0]);
return 1;
}
int fd1 = open(argv[1], O_APPEND|O_WRONLY|O_CREAT, 0644);
if (fd1 < 0)
{
fprintf(stderr, "%s: failed to open/create file %s for writing\n", argv[0], argv[1]);
return 1;
}
int fd2 = open(argv[2], O_RDONLY);
if (fd2 < 0)
{
fprintf(stderr, "%s: failed to open file %s for reading\n", argv[0], argv[2]);
return 1;
}
char c;
while (read(fd2, &c, sizeof(c)) == sizeof(c)) {
if (write(fd1, &c, sizeof(c)) != sizeof(c))
break;
}
close(fd1);
close(fd2);
return 0;
}
I note that you might get warnings about comparing signed and unsigned values with the read() and write() tests — the functions return a ssize_t and sizeof produces a size_t (2 vs 1 letters s). You can cast around that, but it's ugly. The code is also woefully inefficient. Single character reads and writes work, but you get dramatically better performance in general with buffer sizes in the 1 KiB to 4 KiB range, and even bigger. Of course, then you have to trap and use the value returned by read() so that the correct amount of data is written.
Note that the original code would continue the loop if the read() failed (returned -1). It would only stop if the read() was successful at returning 0 when it reached EOF.

create new file with system calls

Im trying to create a new file / overwrite an existing file using systemcalls , but for some reason I have two problems:
1. When I'm first running the program it exits with value 0, so it seems like it created the file successfully, but I can't see anything in my project directory.
then when I secondly running the program the file is created, but an error message is printed on the screen.
2. Also after the first iteration of the program, I can't see the prinf message at the end of the main function.
Thanks for helping.
int readFileDesc = 0, writeFiledesc = 0;
int sourceFile = 1, destFile = 2, bufferSize = 3, isOverwrite;
if (argc != 4 && argc != 5) {
printf("Invalid number of arguments\n");
printf("Usage:\n");
printf(" ex1 [-f] SOURCE DEST BUFFER_SIZE");
exit(EXIT_FAILURE);
}
//Checking if -f [OP] is activated.
isOverwrite = (strcmp(argv[1], "-f") == 0);
if (isOverwrite) {
sourceFile++;
destFile++;
bufferSize++;
}
//Opening the source file
readFileDesc = open(argv[sourceFile], O_RDONLY);
if (readFileDesc < 0) {
perror("Unable to open source file for reading: ");
exit(EXIT_FAILURE);
}
//opening the destination file
if (!isOverwrite) {
//Case we dont have the -f [op] so we create the file.
writeFiledesc = open(argv[destFile],
O_CREAT | O_EXCL | O_WRONLY ,
S_IRUSR | S_IWUSR);
if (writeFiledesc < 0) {
perror("Unable to open destination file for reading: ");
exit(EXIT_FAILURE);
}
} else {
//Case we have the -f [op] so we override existing file.
writeFiledesc = open(argv[destFile], O_RDONLY | O_WRONLY | O_TRUNC);
if (writeFiledesc < 0) {
perror("Unable to open destination file for writing: ");
exit(EXIT_FAILURE);
}
}
//Assume the buffersize is legal.
bufferSize = atoi(argv[bufferSize]);
char data[bufferSize];
int nread, nwrite;
while ((nread = read(readFileDesc, data, bufferSize)) > 0) {
if ((nwrite = write(writeFiledesc, data, nread)) != nread) {
printf("write problem: ");
}
}
// cant see this!
printf("File %s was copied to %s" , argv[sourceFile] , argv[destFile]);
//handling errors
close(sourceFile);
close(destFile);
return EXIT_SUCCESS;
}
This is wrong:
writeFiledesc = open(argv[destFile], O_RDONLY | O_WRONLY | O_TRUNC);
Using both O_RDONLY and O_WRONLY is wrong. You need to use O_RDWR.
Per the POSIX standard for open():
SYNOPSIS
#include <sys/stat.h> #include <fcntl.h>
int open(const char *path, int oflag, ...);
...
Values for oflag are constructed by a bitwise-inclusive OR of flags
from the following list, defined in . Applications shall
specify exactly one of the first five values (file access modes)
below in the value of oflag:
O_EXEC
Open for execute only (non-directory files). The result is unspecified if this flag is applied to a directory.
O_RDONLY
Open for reading only.
O_RDWR
Open for reading and writing. The result is undefined if this flag is applied to a FIFO.
O_SEARCH
Open directory for search only. The result is unspecified if this flag is applied to a non-directory file.
O_WRONLY
Open for writing only.
Any combination of the following may be used:
...
Also, read() and write() return ssize_t, not int.

Copying file fails, EBADF on closing output file descriptor

So I was following a little outdated book (2010) and I'm trying to copy a file with Linux system calls. This is what i have:
NOTE: Ignore the tlpi_hdr.h and error_functions.h, they define errExit() and fatal() and some otheres, they just print the error and exit.
#include <stdio.h>
#include <fcntl.h>
#include "lib/tlpi_hdr.h"
#include "lib/error_functions.h"
#ifndef BUF_SIZE
#define BUF_SIZE 1024
#endif
int main(int argc, char *argv[])
{
int inputFd, outputFd, openFlags;
mode_t filePerms;
ssize_t numRead;
char buf[BUF_SIZE];
if (argc != 3 || strcmp(argv[1], "--help") == 0) {
usageErr("%s old-file new-file\n", argv[0]);
}
inputFd = open(argv[1], O_RDONLY);
if (inputFd == -1) {
errExit("Opening file %s", argv[1]);
}
openFlags = O_CREAT | O_WRONLY | O_TRUNC;
filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
outputFd = open(argv[2], openFlags, filePerms);
if (outputFd == -1) {
errExit("Opening file for writing %s", argv[1]);
}
while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0) {
if (write(outputFd, buf, numRead) != numRead))
fatal("I/O Error");
if (numRead == -1)
fatal("Reading error");
}
if (close(outputFd == -1))
errExit("close input");
if (close(inputFd == -1))
errExit("close output");
return EXIT_SUCCESS;
}
I'm failing on closing of the output file descriptor with EBADF Bad file descriptor:
thinkpad :: ~/.tlpi % ./cp.o a b
ERROR [EBADF Bad file descriptor] close output
The file copies fine tho:
thinkpad :: ~/.tlpi % sha1sum a
40a925a93e149ac53d2630cde8adeb63b8134b29 a
thinkpad :: ~/.tlpi % sha1sum b
40a925a93e149ac53d2630cde8adeb63b8134b29 b
thinkpad :: ~/.tlpi %
Why?
Let's take a closer look at your close call:
close(outputFd == -1)
Here you are comparing outputFd to the value -1. The result of that is a boolean value, which in C will be either 0 or 1. This happens to be either standard input or standard output, depending on the result. Not a file you descriptor you should close.
My guess is that you meant
if (close(outputFd) == -1)

Why stat and fstat return the st_size == 0?

I was testing a code from APUE, in chapter 14(Advanced I/O) of memory map file, the fstat() always return the fdin's st_size as zero, and I tried stat() instead, and also get the same result. I list the code below(I have removed the apue.h dependencies):
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#define COPYINCR (1024*1024*1024) /* 1GB */
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("usage: %s <fromfile> <tofile>", argv[0]);
exit(1);
}
int fdin, fdout;
if ((fdin = open(argv[1], O_RDONLY)) < 0) {
printf("can not open %s for reading", argv[1]);
exit(1);
}
if ((fdout = open(argv[2] /* typo fix */, O_RDONLY | O_CREAT | O_TRUNC)) < 0) {
printf("can not open %s for writing", argv[2]);
exit(1);
}
struct stat sbuf;
if (fstat(fdin, &sbuf) < 0) { /* need size fo input file */
printf("fstat error");
exit(1);
}
// always zero, and cause truncate error (parameter error)
printf("input_file size: %lld\n", (long long)sbuf.st_size);
if (ftruncate(fdout, sbuf.st_size) < 0) { /* set output file size */
printf("ftruncate error");
exit(1);
}
void *src, *dst;
off_t fsz = 0;
size_t copysz;
while (fsz < sbuf.st_size) {
if (sbuf.st_size - fsz > COPYINCR)
copysz = COPYINCR;
else
copysz = sbuf.st_size - fsz;
if (MAP_FAILED == (src = mmap(0, copysz, PROT_READ,
MAP_SHARED, fdin, fsz))) {
printf("mmap error for input\n");
exit(1);
}
if (MAP_FAILED == (dst = mmap(0, copysz,
PROT_READ | PROT_WRITE,
MAP_SHARED, fdout, fsz))) {
printf("mmap error for output\n");
exit(1);
}
memcpy(dst, src, copysz);
munmap(src, copysz);
munmap(dst, copysz);
fsz += copysz;
}
return 0;
}
And then I have tried the Python os.stat, it also get the zero result, why this happened? I have tried these and got the same result on Mac OS (Darwin kernel 13.4) and Ubuntu (kernel 3.13).
UPDATE:
Oh, there was a typo error, I should refer to fdout to argv[2], and the O_TRUNC flag certainly make the fdin to zero. Should I close or delete this question?
The reason why Python's os.stat() also return (stat.st_size == 0) is that I passed the same test file (argv[1]) to test, and the file has been previously truncated to zero (I haven't check its size using ls -lh before passing to os.stat()), and certainly os.stat() return zero.
Do not ask SO questions before you go to bed or in a rush.
Ok, the real problem is double open the same input file, and this does not cause any build or runtime error until the ftruncate().
The first open get a read-only fdin, the second open create a new file (fdout and truncated) to copy from fdin via memory map, and the second open truncated the first file (argv[1]), and cleaned all its content. But the fdin still working with fstat (and certainly), this make me hard to find the reason.
The second part is I always use the same file for testing (generated via dd) and have not checking the size, so the os.stat(/path/to/file) and stat(/path/to/file) also return st_size == 0, this makes me believe that this must be some os-level-prolicy defined the behaviour, and I rushed to Mac OS (using the same typo code), and got the same result (they really consistent on POSIX level, event the bug!), and at last, I came to SO for help.

memory mapped files

I wrote a code for writing the content to the mapped buffer which mapped by using the mmap() system call.
After I did some the changes in the mapped buffer,then I called the msync().It should update to the file on disk.
But,It doesn't made any changes to the file on disk.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#define FILEMODE S_IRWXU | S_IRGRP | S_IROTH
#define MAX 150
main(int argc,char *argv[])
{
int fd,ret,len;
long int len_file;
struct stat st;
char *addr;
char buf[MAX];
if(argc > 1)
{
if((fd = open(argv[1],O_RDWR | O_APPEND | O_CREAT ,FILEMODE)) < 0)
perror("Error in file opening");
if((ret=fstat(fd,&st)) < 0)
perror("Error in fstat");
len_file = st.st_size;
/*len_file having the total length of the file(fd).*/
if((addr=mmap(NULL,len_file,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0)) == MAP_FAILED)
perror("Error in mmap");
len = len_file;
while((fgets(buf,MAX,stdin)) != NULL)
{
strcat(addr+len,buf);
printf( "Val:%s\n",addr ) ; //Checking purpose
len = len + (strlen(buf));
}
if((msync(addr,len,MS_SYNC)) < 0)
perror("Error in msync");
if( munmap(addr,len) == -1)
printf("Error:\n");
printf("addr %p\n",addr);
}
else
{
printf("Usage a.out <filename>\n");
}
}
If you want your changes to be reflected in the on-disk file, you must map the file as MAP_SHARED, not MAP_PRIVATE.
Additionally, you cannot extend the file simply by writing beyond the end of the mapping. You must use ftruncate() to extend the file to the new size, then change the mapping to include the new portion of the file. The portable way to change the mapping is to unmap the mapping then recreate it with the new size; on Linux you can instead use mremap().
Your len and len_file variables should be of type size_t, and you should use memcpy() rather than strcat(), since you know exactly the length of the string, exactly where you want to copy it, and you don't want to copy the null-terminator.
The following modification of your code works on Linux (using mremap()) :
#define _GNU_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#define FILEMODE S_IRWXU | S_IRGRP | S_IROTH
#define MAX 150
int main(int argc,char *argv[])
{
int fd, ret;
size_t len_file, len;
struct stat st;
char *addr;
char buf[MAX];
if (argc < 2)
{
printf("Usage a.out <filename>\n");
return EXIT_FAILURE;
}
if ((fd = open(argv[1],O_RDWR | O_CREAT, FILEMODE)) < 0)
{
perror("Error in file opening");
return EXIT_FAILURE;
}
if ((ret = fstat(fd,&st)) < 0)
{
perror("Error in fstat");
return EXIT_FAILURE;
}
len_file = st.st_size;
/*len_file having the total length of the file(fd).*/
if ((addr = mmap(NULL,len_file,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0)) == MAP_FAILED)
{
perror("Error in mmap");
return EXIT_FAILURE;
}
while ((fgets(buf,MAX,stdin)) != NULL)
{
len = len_file;
len_file += strlen(buf);
if (ftruncate(fd, len_file) != 0)
{
perror("Error extending file");
return EXIT_FAILURE;
}
if ((addr = mremap(addr, len, len_file, MREMAP_MAYMOVE)) == MAP_FAILED)
{
perror("Error extending mapping");
return EXIT_FAILURE;
}
memcpy(addr+len, buf, len_file - len);
printf( "Val:%s\n",addr ) ; //Checking purpose
}
if((msync(addr,len,MS_SYNC)) < 0)
perror("Error in msync");
if (munmap(addr,len) == -1)
perror("Error in munmap");
if (close(fd))
perror("Error in close");
return 0;
}
Note that you've provided a mapping for the file that is exactly the size of the file. If you create the file in your call to open(2), it will have a length of 0, and I wouldn't be surprised if the kernel doesn't bother setting up any kind of memory mapping from a 0 length mapping. (Maybe it does? I've never tried...)
I would suggest using ftruncate(2) to extend the length of your file before performing the mapping. (Note that extending files using ftruncate(2) isn't very portable; not all platforms provide extending functionality and not all filesystem drivers support the extending functionality. See your system's manpage for details.)
You must use the MAP_SHARED mapping for your file modifications to be saved to disk.
Your use of perror(3) isn't quite correct; perror(3) will not terminate your program, so it will continue executing with incorrect assumptions:
if((ret=fstat(fd,&st)) < 0)
perror("Error in fstat");
Should read:
if((ret=fstat(fd,&st)) < 0) {
perror("Error in fstat");
exit(1);
}
(Or exit(EXIT_FAILURE) if you want to be more portable -- I find that a little harder on the eyes but I live in Linux-land.)
strcat(3) expects to find an ASCII NUL character (byte value 0x00, C representation '\0') -- the usual C end-of-string marker -- at the end of the dest string. Your file will not contain an ASCII NUL if you create it in this program -- its length is zero, after all -- and I don't know the consequences of trying to read a zero-byte file via mmap(2). If the file already exists and has data in it, it probably doesn't have an ASCII NUL encoded in the file. strcat(3) is almost certainly the wrong tool to write into your file. (No one wants ASCII NULs in their files anyway.) Try memcpy(3) instead.

Resources