LINUX.ORG.RU

Копирование фрагмента файла - учебная программа

 , ,


0

1

Просьба проверить учебную программу на языке Си на наличие ошибок.

Программа предназначена для копирования фрагмента файла source в файл destination. Фрагмент задается смещением offset, длиной length.
Утилита dd выполняет указанную задачу медленно при условии, что bs < 512 байт.

Порядок сборки программы:

cc -Wall -Wextra -O2 -o subcp subcp.c

Порядок запуска:

./subcp source_file fragment_offset fragment_length destination_file

В случае обнаружения ошибок, недочетов в программе просьба сообщить.
Спасибо.

Исходный код файла «subcp.c»:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // stat()
#define MiB (1<<20)

void err (const char* s); // show error message and exit
int fcopy (void* buf, size_t size, FILE* srcfd, FILE* dstfd); // copy SIZE bytes from SRCFD to DSTFD using buffer BUF
off_t fsize (const char* path); // get file size
void perr (const char* s); // show perror(s) and exit
long stol (const char* s); // strtol() wrapper

int main (int argc, char** argv)
{
  FILE *srcfd, *dstfd; // source, destination file descriptor
  char *srcpath, *dstpath; // source, destination file name
  off_t srcsize; // source file size
  long off, len; // offset and length of fragment to be copied
  int status; // exit status

  if (argc != 5) err ("Usage: cpy SRC OFF LEN DST");

  srcpath = argv[1];
  dstpath = argv[4];
  if (strcmp (srcpath, dstpath) == 0) err ("SRC and DST should be different files");

  srcsize = fsize (srcpath);
  if (srcsize == 0) err ("nothing to copy: SRC file is empty");

  off = stol (argv[2]);
  if (off < 0 || off > (srcsize-1)) err ("wrong OFF value for use with SRC file");

  len = stol (argv[3]);
  if (len < 1 || len > (srcsize-off)) err ("wrong LEN value for use with SRC file");

  printf ("SRCSIZE=%ld, OFF=%ld, LEN=%ld\n", srcsize, off, len); // PRINT DEBUG INFO

  srcfd = fopen (srcpath, "rb");
  if (srcfd == NULL) perr ("fopen(SRC)");

  status = EXIT_FAILURE;

  dstfd = fopen (dstpath, "wb");
  if (dstfd == NULL) {
    perror ("fopen(DST)");
    goto close_src;
  }

  // set offset for SRCFD file
  if (fseek (srcfd, off, SEEK_SET) == -1) {
    perror ("fseek(SRC)");
    goto close_dst;
  }

  /* copy "len" bytes from SRCFD to DSTFD using 64M chunks and remainder:
     len = num * 64M + rem */
  { 
    void* buf; // memory buffer
    size_t
      size, // buf size
      num, // number of 64M chunks to copy
      rem, // length of remainder
      i;

    size = 64 * MiB;
    num = len / size;

    if (num == 0) size = rem = len; // if (len < 64M) buf = malloc (len)
    else rem = len % size; // else calculate "rem"; buf = malloc (64M)

    buf = malloc (size);
    if (buf == NULL) {
      perror ("malloc");
      goto free_buf;
    }

    // copy 64M chunks
    for (i=0; i<num; i++)
      if (fcopy (buf, size, srcfd, dstfd) == -1)
        goto free_buf;

    // copy fragment remainder
    if (rem != 0)
      if (fcopy (buf, rem, srcfd, dstfd) == -1)
        goto free_buf;

    // copying complete
    status = EXIT_SUCCESS;

  free_buf:
    free (buf);
  }

close_dst:
  if (fclose (dstfd) == EOF) perror ("fclose(DST)");

close_src:
  if (fclose (srcfd) == EOF) perror ("fclose(SRC)");

  exit (status);
}

void err (const char* s) {
  fprintf (stderr, "%s\n", s);
  exit (EXIT_FAILURE);
}

int fcopy (void* buf, size_t size, FILE* srcfd, FILE* dstfd) {
  if (fread (buf, size, 1, srcfd) == 0) {
    if (ferror (srcfd)) perror ("fread(SRC)");
    else fputs ("unexpected end of SRC file\n", stderr); // should not happen
    return -1;
  }

  if (fwrite (buf, size, 1, dstfd) == 0) {
    perror ("fwrite(DST)");
    return -1;
  }

  return 0;
}

off_t fsize (const char* path) {
  struct stat s;
  if (stat (path, &s) == -1) perr ("stat(SRC)");
  return s.st_size;
}

void perr (const char* s) {
  perror (s);
  exit (EXIT_FAILURE);
}

long stol (const char* s) {
  long ret;
  errno = 0;
  ret = strtol (s, NULL, 0);
  if (errno) perr ("strtol");
  return ret;
}


Твой велосипед при помощи mmap сжался бы до страницы-другой.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

На mmap всосёшь на page fault постоянных, а они очень дорогие.

mix_mix ★★★★★ ()

Строка 58, напрашивается метка. Если метку не надо лучше убрать скобки.

Dron ★★★★★ ()
Ответ на: комментарий от Eddy_Em

Ты вообще в курсе как работает mmap, в курсе как устроена виртуальная память, страничная адресация, что один page fault стоит от 1000 тактов на amd64? Зачем делать плохо, когда можно сделать хорошо?

mix_mix ★★★★★ ()
Ответ на: комментарий от mix_mix

а шо, fopen, fread и malloc в лялихе уже без mmap, виртуальной или памяти и страничной адресации реализованы, лолушка?

MyTrooName ★★★★★ ()
Последнее исправление: MyTrooName (всего исправлений: 4)
Ответ на: комментарий от MyTrooName

sendfile сам по себе системный вызов. Всю работу делает ядро. А если ему потребуется аллоцировать внутренний буффер, то не факт, что это приведет к page fault. Так что вроде всё ок.

В мане FreeBSD написано, что выходной дескриптор должен указывать на сокет, так что я хз, можно ли им с диска на диск файлы копировать.

Если использовать read (2) или write (2), то разницы точно никакой.

Хотя не пофиг ли?

lisper-pipisper ()
Ответ на: комментарий от mix_mix

Мне насрать. Я знаю, что mmap в любом случае будет дешевле, чем самому городить велосипеды.

Ну, конкретно эту задачу можно было бы и без mmap решить. Я бы первым вариантом взял mmap, а вторым — обычное чтение в лоб (т.е. читал бы кусками по 4 мегабайта и писал бы в файл назначения). Тоже было бы максимум пара страниц текста (если не учитывать разбор командной строки).

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от MyTrooName

С mmap мы на каждую страницу получаем page fault, дурачок. Подумай в чем отличие от read/write с фиксированным буфером.

mix_mix ★★★★★ ()
Ответ на: комментарий от Eddy_Em

Я знаю, что mmap в любом случае будет дешевле, чем самому городить велосипеды.

Это далеко не так.

mix_mix ★★★★★ ()
Ответ на: комментарий от lisper-pipisper

В мане FreeBSD написано, что выходной дескриптор должен указывать на сокет

В мане Linux написано, что начиная с 2.6.33 можно и с диска на диск.

kim-roader ★★ ()
Ответ на: комментарий от Eddy_Em

Сколько тебя уже можно носом в говно тыкать?
http://stackoverflow.com/questions/45972/mmap-vs-reading-blocks
http://stackoverflow.com/questions/258091/when-should-i-use-mmap-for-file-access
http://stackoverflow.com/questions/12383900/does-mmap-really-copy-data-to-the...

TL;DR:

mmap is faster than read only in the use cases which it favors — random access and page reuse

mix_mix ★★★★★ ()
Ответ на: комментарий от Eddy_Em

Вот тебе даже сам Линус пишет «f*ck you», комментируя тройной отсос mmap перед read. К слову, сейчас не сильно лучше ситуация, спустя 15-то лет. Хотя, какой толк в том, что я тебе всё это пишу, ты же как обычно ответишь, что тебе на всё похрен.

mix_mix ★★★★★ ()
Последнее исправление: mix_mix (всего исправлений: 1)
Ответ на: комментарий от mix_mix

Чиатй:

а вторым — обычное чтение в лоб

Я понимаю, что если сравнивать тупое чтение и mmap, то mmap ввиду избыточности проиграет. Но если сравнивать простоту реализации говновелосипеда, то сам понимаешь.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

Так мы здесь рассуждаем не про простоту реализации, а про производительность. В любом случае, в обеих номинациях победит sendfile.

mix_mix ★★★★★ ()
Ответ на: комментарий от mix_mix

Хрен тебе! По производительности победит тупой read/write блоками по 4МБ!

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от lisper-pipisper

В мане FreeBSD написано, что выходной дескриптор должен указывать на сокет

Здравствуйте, это сайт о FreeBSD? НЕТ! Поэтому читай правильные маны:

In Linux kernels before 2.6.33, out_fd must refer to a socket. Since Linux 2.6.33 it can be any file.

Более того, POSIX_FADV_SEQUENTIAL + sendfile - самый быстрый способ копирования файлов (особенно в /dev/null)

kawaii_neko ★★★ ()
Ответ на: комментарий от Eddy_Em

Но если сравнивать простоту реализации говновелосипеда, то сам понимаешь

Давай сравним. Вот cat на основе sendfile:

#include <unistd.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    int i;
    for (i = 1; i < argc; i++) {
        struct stat st;
        if (stat(argv[i], &st) == 0 && S_ISREG(st.st_mode)) {
            off_t off = 0;
            int fd = open(argv[i], O_RDONLY);
            if (fd == -1)
                continue;
            posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
            while (off < st.st_size) {
                ssize_t bytes = sendfile(STDOUT_FILENO, fd, &off, st.st_size - off);
                if (bytes <= 0)
                    break;
            }
            close(fd);
        }
    }
    return 0;
}
А теперь запили на mmap с корректной обработкой EIO, а мы с пацанами посмеемся с тебя.

kawaii_neko ★★★ ()

Просьба проверить учебную программу на языке Си на наличие ошибок.

честно? Без обид?

Просто намекну:

status = EXIT_SUCCESS;

srcfs =  fopen(src, "r");
if(!srcfs) perr("…");
destfs = fopen(dest, "w");
if(!destfs) perr("…");

if(srcfs && destfs)
  status = f(srcfs, destfs);
else
  status = EXIT_FAILURE;

if(destfs) fclose(destfs);
if(srcfs)  fclose(srcfs);

надеюсь намёк понятен.

emulek ()
Ответ на: комментарий от kawaii_neko

posix_fadvise

Забавно. Первый раз такой велосипед вижу.

ssize_t bytes = sendfile(STDOUT_FILENO, fd, &off, st.st_size - off);

Ну так эта хрень решается аналогично, в две строчки:

char *ptr = my_mmap(filename);
printf("%s\n", ptr);

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от mix_mix

Мне тупо лень искать исходники sendfile, но если действительно эта хрень — грязный хак над ведром (т.е. оно каким-то чудейсвтенным образом работает быстрее, чем read/write; скажем, использует железячные возможности драйвера HDD, который тупо копирует блоки, не передавая их в оперативу), тогда да, оно быстрей будет.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

Ну так эта хрень решается аналогично, в две строчки

Копирование бинарных файлов через printf «%s», да вот это профессионализм. Полагаю, что что будет с mmap, если файл не удается прочитать, ты даже не догадываешься, а как обработать и вовсе не имеешь понятия.

kawaii_neko ★★★ ()
Ответ на: комментарий от kawaii_neko

Ты про "насрать" слышал? В ТЗ такого не было. А если будет, то пжалста, решим.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

Ты про «насрать» слышал?

А, так ты на C ничего серьезнее анекдотов для тралленга студентов не писал. Ну так бы сразу и сказал, а то я уж было собрался твоим просвещением заняться.

kawaii_neko ★★★ ()
Ответ на: комментарий от Eddy_Em

Забавно. Первый раз такой велосипед вижу.
Копирование бинарных файлов через printf %s
Мне тупо лень искать исходники sendfile
Ты про «насрать» слышал?

Какой же ты кретин.

mix_mix ★★★★★ ()
Ответ на: комментарий от kawaii_neko

Пишу все, что мне нужно для работы: от скриптиков до тяжелых вычислений. И мне совершенно насрать на стандарты и мнение погромистов, т.к. погромисты все равно к моему коду никакого отношения не имели, не имеют и не будут иметь: эти жиды уж больно жиробасные! Дохрена хотят. За зарплату работать не хотят, ну и ладно — сам сделаю!

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

Эдик, если ты такой альтернативно одарённый, то пожалуйста, варись в своём невежестве, но не смей, повторяю уже в который раз, не смей давать свои «советы» другим.

mix_mix ★★★★★ ()
Ответ на: комментарий от kawaii_neko

Здравствуйте, это сайт о FreeBSD? НЕТ!

Я вообще не знаю, о чём этот сайт.

Поэтому читай правильные маны:

Ты мне ещё рассказывать будешь, что правильно, а что нет.

Однако рад за ваш линукс. Правда не понятно, кто будет так делать, раз это не портабельно (как и весь sendfile, собсно)

lisper-pipisper ()
Ответ на: комментарий от Eddy_Em

скажем, использует железячные возможности драйвера HDD

Дикое предположение. Просто оно работает в ядре, а с read или write наверняка придется копировать эти буфферы из ядра в юзерспейс и наоборот

lisper-pipisper ()
Ответ на: комментарий от lisper-pipisper

раз это не портабельно

Лол, куда портировать-то собрался? Под darwin на arm'е? Не устаю проигрываться над «пишупереносимыйкод»-неудачниками уже лет 5.

kawaii_neko ★★★ ()
Ответ на: комментарий от Eddy_Em

В мане внутреннее устройсто этого дела не напишут. Тем более, что в DragonFly/FreeBSD каждое устройство реализует этот метод заново, не знаю как с vfs

lisper-pipisper ()
Ответ на: комментарий от kawaii_neko

Не устаю проигрываться над «пишупереносимыйкод»-неудачниками уже лет 5.

Не знаю, кому что ты проигрываешь. Честно говоря, это никого кроме твоей мамки не интересует

lisper-pipisper ()
Ответ на: комментарий от lisper-pipisper

Я зря человека обругал. Действительно ведь, самый шустрый вариант. И в мане так написано. RTFM! Надо себе такую татуху на левой ладони сделать, чтобы когда по морде себя хлопал при косяках, видно было!

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

Надеюсь, в скором времени ты себя окончательно прихлопнешь.

postman_ ★☆ ()
Ответ на: комментарий от kawaii_neko

Здравствуйте, это сайт о FreeBSD? НЕТ!

Вообще-то да. Это раздел «программирование и разработка ПО под Linux/Unix» сайта opensource.ru. Внезапно, да?

hateyoufeel ★★★★★ ()

Если я не ошибаюсь, fread/fwrite уже использует буферы внутри себя. И если читать fread'ом в свой буфер, то получится двойная буферизация

makoven ★★★★★ ()

проверяйте сами, есть куча средств, некоторые из них: valgrind, efence + gdb, вторая мне нравится больше, или готовьте деньги.

IvanR ★★ ()
Последнее исправление: IvanR (всего исправлений: 1)
Ответ на: комментарий от kawaii_neko

На, таки навелосипедил я с mmap:

#include <unistd.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/mman.h>

double dtime(){
	double t;
	struct timeval tv;
	gettimeofday(&tv, NULL);
	t = tv.tv_sec + ((double)tv.tv_usec)/1e6;
	return t;
}

typedef struct{
	char *data;
	size_t len;
} mmapbuf;

void My_munmap(mmapbuf **b){
	if(munmap((*b)->data, (*b)->len))
		perror("Can't munmap");
	free(*b);
	*b = NULL;
}

mmapbuf *My_mmap(char *filename){
	int fd;
	char *ptr = NULL;
	size_t Mlen;
	struct stat statbuf;
	if((fd = open(filename, O_RDONLY)) < 0){
		perror("Can't open file for reading");
		goto ret;
	}
	if(fstat (fd, &statbuf) < 0){
		perror("Can't stat file");
		goto ret;
	}
	Mlen = statbuf.st_size;
	if((ptr = mmap (0, Mlen, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){
		perror("Mmap error for input");
		goto ret;
	}
	mmapbuf *ret = calloc(sizeof(mmapbuf), 1);
	if(ret){
		ret->data = ptr;
		ret->len = Mlen;
	}else munmap(ptr, Mlen);
ret:
	if(close(fd)) perror("Can't close mmap'ed file");
	return  ret;
}


int main(int argc, char **argv){
	struct stat st;
	double T0;
	if(argc != 2){
		printf("Usage: %s file\n", argv[0]);
		return 2;
	}
	if (stat(argv[1], &st) == 0 && S_ISREG(st.st_mode)){
		off_t off = 0;
		int fd = open(argv[1], O_RDONLY);
		int fdo = open("tmpoutput", O_WRONLY | O_CREAT,  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
		if (fd < 0 || fdo < 0){
			perror("can't open");
			return 1;
		}
		T0 = dtime();
		posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
		while (off < st.st_size) {
			ssize_t bytes = sendfile(fdo, fd, &off, st.st_size - off);
			if (bytes <= 0){
				perror("can't sendfile");
			}
		}
		close(fd);
		close(fdo);
		printf("Copied by sendfile, time: %gs\n", dtime()-T0);
		T0 = dtime();
		mmapbuf *map = My_mmap(argv[1]);
		if(map){
			fdo = open("tmpoutput", O_WRONLY | O_CREAT,  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
			if (fd < 0 || fdo < 0){
				perror("can't open");
				return 1;
			}
			size_t written = 0, towrite = map->len;
			char *ptr = map->data;
			do{
				ssize_t wr = write(fdo, ptr, towrite);
				if(wr <= 0) break;
				written += wr;
				towrite -= wr;
				ptr += wr;
			}while(towrite);
			if(written != map->len){
				printf("err: writed only %zd byted of %zd\n", written, map->len);
			}
			close(fdo);
			My_munmap(&map);
			printf("Copied by mmap, time: %gs\n", dtime()-T0);
		}
	}else
		perror("Can't stat file");
	return 0;
}
Запускаем на большом файле (чтобы наверняка буферы в оперативе не схоронились):
./a.out Titanik.mp4 
Copied by sendfile, time: 30.1055s
Copied by mmap, time: 35.6469s

du Titanik.mp4 
2.6G	Titanik.mp4
Так что, вполне шустро mmap отработал. Конечно, медленнее, т.к.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

sendfile внутри ведра по сути работает, без юзверьспейса.

А за функцию sendfile спасибо. Не знал про нее — добавил себе в сниппеты.

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

на большом файле (чтобы наверняка буферы в оперативе не схоронились

2.6G

Большом, ага.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

У меня оперативки всего-то 2ГБ. Так что, нормально!

Eddy_Em ☆☆☆☆☆ ()
Ответ на: комментарий от Eddy_Em

чтобы наверняка буферы в оперативе не схоронились

Это легко делается наверняка.

#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    int i;
    for (i = 1; i < argc; i++) {
        int fd = open(argv[i], O_RDONLY);
        if (fd == -1)
            continue;
        posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
        close(fd);
    }
    return 0;
}

kawaii_neko ★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.