I have a program that take two files Source and Destination.
I am trying to Copies the SOURCE file to the DESTINATION file. If DESTINATION does not exist, it is created. If DESTINATION exists and is a file, it is overwritten. If DESTINATION exists and is a directory, the source files will be copied there.
How can i copy the source file into the destination directory using calls system ?
What i tried :
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include "checksum.h"
int main (int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: lcp [-b taille] source... destination\n");
exit(1);
}
int fd1 = open(argv[1], O_RDONLY, 0644); //0644 — owner can read or write, group and others can only read; or something more restrictive).
if (fd1 < 0)
{
fprintf(stderr, "%s: failed to open file %s for reading\n", argv[0], argv[1]);
return 1;
}
int fd2 = open(argv[2], O_RDWR|O_CREAT);
if (fd2 < 0)
{
fprintf(stderr, "%s: failed to open file %s for reading and writing\n", argv[0], argv[2]);
return 1;
}
close(fd1);
close(fd2);
return 0;
}
CodePudding user response:
You just need a loop that does a read
on a buffer and a write
of that buffer.
Note that most of the time if you ask read
to get (e.g.) 100 bytes, it will do so for most files. It may return less than that, particularly if the file size is not a multiple of 100. So, we must account for "short" reads (and errors). Likewise for write
Here is the refactored code. It is annotated:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
//#include "checksum.h"
// xread -- read in data (account for short reads and errors)
// RETURNS: number of bytes read
ssize_t
xread(int fd,void *buf,size_t buflen)
{
unsigned char *bp = buf;
ssize_t curlen;
ssize_t totlen = 0;
// continue reading until end of buffer or read error
for (; buflen > 0; buflen -= curlen, totlen = curlen) {
// read next chunk
// NOTE: curlen may be less than buflen
curlen = read(fd,&bp[totlen],buflen);
// got an EOF
if (curlen == 0)
break;
// read error
if (curlen < 0) {
// handle interruptions from signals
if (errno == EINTR)
continue;
perror("xread");
exit(1);
}
}
return totlen;
}
// xwrite -- write out data (account for short reads and errors)
// RETURNS: number of bytes written
ssize_t
xwrite(int fd,const void *buf,size_t buflen)
{
const unsigned char *bp = buf;
ssize_t curlen;
ssize_t totlen = 0;
// continue writing until end of buffer or write error
for (; buflen > 0; buflen -= curlen, totlen = curlen) {
// write next chunk
// NOTE: curlen may be less than buflen
curlen = write(fd,&bp[totlen],buflen);
// got EOF (NOTE: this should _never_ happen)
if (curlen == 0)
break;
// write error
if (curlen < 0) {
// handle interruptions from signals
if (errno == EINTR)
continue;
perror("xwrite");
exit(1);
}
}
return totlen;
}
int
main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage: lcp [-b taille] source... destination\n");
exit(1);
}
// 0644 — owner can read or write, group and others can only read; or
// something more restrictive).
int fd1 = open(argv[1], O_RDONLY, 0644);
if (fd1 < 0) {
fprintf(stderr, "%s: failed to open file %s for reading\n",
argv[0], argv[1]);
return 1;
}
int fd2 = open(argv[2], O_RDWR | O_CREAT, 0644);
if (fd2 < 0) {
fprintf(stderr, "%s: failed to open file %s for reading and writing\n",
argv[0], argv[2]);
return 1;
}
// loop through file and copy bytes
unsigned char buf[64 * 1024];
while (1) {
ssize_t rlen = xread(fd1,buf,sizeof(buf));
if (rlen <= 0)
break;
xwrite(fd2,buf,rlen);
}
close(fd1);
close(fd2);
return 0;
}
Note: As Andrew pointed out, the open
with O_CREAT
needs a mode argument
UPDATE:
If destination is a directory, the code fails spectacularly. And don't just say failed to open. Please strerror the reason. – – user58697
Yes, I only added the read/write
loop since that was the main problem.
@user58697 I had the same problem that blocked me, do you have any idea how to fix this... – elfii
Okay, there are a few ways to solve this (which was part of the original problem that I missed):
- Use
stat
on the output file to decide if it's a directory - Check
errno
from the open and see if it'sEISDIR
While both are valid, I chose (2):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
//#include "checksum.h"
// xread -- read in data (account for short reads and errors)
// RETURNS: number of bytes read
ssize_t
xread(int fd,void *buf,size_t buflen)
{
unsigned char *bp = buf;
ssize_t curlen;
ssize_t totlen = 0;
// continue reading until end of buffer or read error
for (; buflen > 0; buflen -= curlen, totlen = curlen) {
// read next chunk
// NOTE: curlen may be less than buflen
curlen = read(fd,&bp[totlen],buflen);
// got an EOF
if (curlen == 0)
break;
// read error
if (curlen < 0) {
// handle interruptions from signals
if (errno == EINTR)
continue;
perror("xread");
exit(1);
}
}
return totlen;
}
// xwrite -- write out data (account for short reads and errors)
// RETURNS: number of bytes written
ssize_t
xwrite(int fd,const void *buf,size_t buflen)
{
const unsigned char *bp = buf;
ssize_t curlen;
ssize_t totlen = 0;
// continue writing until end of buffer or write error
for (; buflen > 0; buflen -= curlen, totlen = curlen) {
// write next chunk
// NOTE: curlen may be less than buflen
curlen = write(fd,&bp[totlen],buflen);
// got EOF (NOTE: this should _never_ happen)
if (curlen == 0)
break;
// write error
if (curlen < 0) {
// handle interruptions from signals
if (errno == EINTR)
continue;
perror("xwrite");
exit(1);
}
}
return totlen;
}
int
main(int argc, char **argv)
{
if (argc < 3) {
fprintf(stderr, "Usage: lcp [-b taille] source... destination\n");
exit(1);
}
// input file
char *ifile = argv[1];
char *itail = ifile;
int fd1;
// output file
char *otail = argv[2];
char ofile[strlen(otail) 1 strlen(ifile) 1];
int fd2;
// 0644 — owner can read or write, group and others can only read; or
// something more restrictive).
fd1 = open(ifile, O_RDONLY);
if (fd1 < 0) {
fprintf(stderr, "%s: failed to open file %s for reading -- %s\n",
argv[0], itail, strerror(errno));
return 1;
}
// try original output file and then [if directory] the concatenated file
for (int tryno = 1; tryno <= 2; tryno) {
// open output assuming arg is flat file
fd2 = open(otail, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd2 >= 0)
break;
// if output is not a directory, there is another error
if (errno != EISDIR)
break;
// get file tail from input file
// (e.g. foo/bar --> bar
itail = strrchr(ifile,'/');
if (itail != NULL)
itail;
else
itail = ifile;
// create output file: otail / itail
strcpy(ofile,otail);
strcat(ofile,"/");
strcat(ofile,itail);
otail = ofile;
}
// stop if output could not be opened
if (fd2 < 0) {
fprintf(stderr, "%s: failed to open file %s for writing -- %s\n",
argv[0], otail, strerror(errno));
return 1;
}
// loop through files and copy bytes
unsigned char buf[64 * 1024];
while (1) {
ssize_t rlen = xread(fd1,buf,sizeof(buf));
if (rlen <= 0)
break;
xwrite(fd2,buf,rlen);
}
close(fd1);
close(fd2);
return 0;
}
CodePudding user response:
If DESTINATION exists and is a file, it is overwritten. If DESTINATION exists and is a directory, the source files will be copied there.
The easiest way to address this is to check how open
failed`:
int fd2 = open(argv[2], O_RDWR|O_CREAT);
if (fd2 < 0) {
if (errno != EISDIR) {
// It is a real failure. Log it, and quit
fprintf("Opening %s: %s\n", argv[2], strerror(errno));
return 1;
}
// argv[2] names a directory. Act acordingly.
Acting accordingly depends on the precise spec. Consider a scenario:
> mkdir foo
> copy bar/baz/blah foo
As a result, one may expect a destination to be foo/blah
. Another may expect it to be foo/bar/baz/blah
.
In any case, you shall construct - according to the spec - the true destination filename from argv[1]
relative to argv[2]
, and open it for writing. Few calls to mkdir
may also be required.