Пример 12

/* Программа, совмещающая команды mv и cp. Иллюстрация работы с файлами.
 * Пример того, как программа может выбирать род работы
 * по своему названию.
 * Компиляция:
 *              cc cpmv.c -o copy ; ln copy move
 * По мотивам книги М.Дансмура и Г.Дейвиса.
 */
#include <stdio.h>              /* буферизованный ввод/вывод */
#include <sys/types.h>          /* системные типы данных */
#include <sys/stat.h>           /* struct stat           */
#include <fcntl.h>              /* O_RDONLY              */
#include <errno.h>              /* системные коды ошибок */
/* #define strrchr rindex           /* для версии ДЕМОС (BSD)    */
extern char *strrchr(char *, char); /* из библиотеки libc.a      */
extern int   errno;
char    MV[] = "move"; char CP[] = "copy";
#define OK      1       /* success - успех   */
#define FAILED  0       /* failure - неудача */
#define YES     OK
#define NO      0
/* Выделить базовое имя файла:
 *     ../wawa/xxx  -->   xxx
 *     zzz          -->   zzz
 *     /            -->   /
 */
char *basename( char *name ){
	char *s      = strrchr( name , '/' );
	return (s    == NULL) ? name : /* нет слэшей       */
	       (s[1] == '\0') ? name : /* корневой каталог */
				s + 1;
}
#define ECANTSTAT (-1)  /* файл не существует */
struct ftype {
       unsigned type;  /* тип файла */
       dev_t    dev;   /* код устройства, содержащего файл */
       ino_t    ino;   /* индексный узел файла на этом устройстве */
};
/* Получение типа файла */
struct ftype filetype( char *name /* имя файла */   )
{
	struct stat st; struct ftype f;
	if( stat( name, &st ) < 0 ){
		 f.type = ECANTSTAT; f.dev = f.ino = 0;
	} else { f.type = st.st_mode & S_IFMT;
		 f.dev  = st.st_dev; f.ino = st.st_ino;
	}
	return f;
}
/* Удаляет файлы, кроме устройств */
int unlinkd( char *name, unsigned type )
{
	if( type == S_IFBLK || type == S_IFCHR || type == S_IFDIR)
		return 0;
	return unlink( name );
}
/* Функция нижнего уровня: копирование информации большими порциями */
int copyfile( int from, int to )
	/* from - дескриптор откуда */
	/* to   - дескриптор куда   */
{
	char buffer[ BUFSIZ ];
	int n; /* число прочитанных байт */
	while(( n = read( from, buffer, BUFSIZ )) > 0 )
	/* read возвращает число прочитанных байт,
	 * 0 в конце файла
	 */
		    if( write( to,  buffer, n ) != n ){
			printf( "Write error.\n" );
			return FAILED;
		    }
	return OK;
}
/* Копирование файла */
int docopy(char *src, char *dst, unsigned typefrom, unsigned typeto)
{       int retc; int fdin, fdout;
	printf( "copy %s --> %s\n", src, dst );
	if((fdin = open( src, O_RDONLY )) < 0 ){
		printf( "Сan't read %s\n", src );
		return FAILED;
	}
	if((fdout = creat( dst, 0644 )) < 0 ){  /* rw-r--r-- */
		printf( "Can't create %s\n", dst );
		return FAILED;
	}
	retc = copyfile( fdin, fdout );
	close( fdin ); close( fdout );
	return retc;
}
/* Переименование файла. Вернуть OK, если удачно, FAILED - неудачно */
int mlink(char *src, char *dst, unsigned typefrom, unsigned typeto)
{
	switch( typefrom ){
	case S_IFDIR:           /* переименование каталога */
		printf( "rename directory %s --> %s\n", src, dst );
		if( access( dst, 0 ) == 0 ){
		/* 0 - проверить существование файла */
			printf( "%s exists already\n", dst );
			/* файл уже существует */
			return FAILED;
		}
		if( link( src, dst ) < 0 ){
		    printf( "Can't link to directory %s\n", dst );
		    perror( "link" );
		 /* Возможно, что для выполнения link() для каталогов,
		  * программа должна обладать правами суперпользователя.
		  */
		    return FAILED;
		}
		unlink( src );
		return OK;
	default:   /* dst - не существует или обычный файл */
		printf( "move %s --> %s\n", src, dst );
		unlinkd( dst, typeto );
		/* зачищаем место, т.к. link()
		 * отказывается выполняться, если
		 * файл dst уже существует (errno==EEXIST).
		 */
		if( link( src, dst ) < 0 ) return FAILED;
		unlinkd( src, typefrom );  /* удаляем старый файл */
		return OK;
	}
}
/* Если не получилось связать файл при помощи link() - следует
 * скопировать файл в указанное место, а затем уничтожить старый файл.
 */
int mcopy(char *src, char *dst, unsigned typefrom, unsigned typeto)
{
	if( typefrom == S_IFDIR )
		return FAILED;
	/* каталог не копируем, поскольку непосредственная запись
	 * в каталог (как целевой файл) разрешена только ядру ОС.
	 */
	return docopy( src, dst, typefrom, typeto );
}
/* Переименование файла */
int domove(char *src, char *dst, unsigned typefrom, unsigned typeto)
{
	switch( typefrom ){
	default:
	   if( ! mlink( src, dst, typefrom, typeto)){
		 if( ! mcopy( src, dst, typefrom, typeto)){
		       printf( "Can't move %s\n", src );
		       return FAILED;
		 } else unlinkd( src, typefrom ); /* стереть старый */
	   }
	   break;
	case S_IFDIR: /* каталог переименовываем в каталог */
	   if( ! strcmp( ".", basename(src))){
		 printf( "impossible to move directory \".\"\n" );
		 return FAILED;
	   }
	   if( ! mlink( src, dst, typefrom, typeto )){
		 if( errno == EXDEV )
		     printf( "No cross device directory links\n" );
		 return FAILED;
	   }
	   break;
	case ECANTSTAT:
	   printf( "%s does not exist\n", src );
	   return FAILED;
	}
	return OK;    /* okay */
}
int docpmv( char *src,   /* файл-источник   */
	    char *dst,   /* файл-получатель */
	    struct ftype typeto, /* тип файла-получателя              */
	    int cp,      /* 0 - переименование, 1 - копирование       */
	    int *confirm /* запрашивать подтверждение на перезапись ? */
){
	struct ftype typefrom;  /* тип источника       */
	char namebuf[BUFSIZ];   /* новое имя получателя (если надо)   */
	typefrom = filetype(src);
	if(typefrom.type == ECANTSTAT){ /* не существует */
	   printf("%s does not exist.\n", src);
	   return FAILED;
	}
	if( typefrom.type != S_IFDIR && typeto.type == S_IFDIR ){
		/* файл в каталоге dst */
		sprintf(namebuf, "%s/%s", dst, basename(src));
		typeto = filetype(dst = namebuf);
	}
	if(typefrom.dev == typeto.dev && typefrom.ino == typeto.ino){
	/* Нельзя копировать файл сам в себя */
	   printf("%s and %s are identical.\n", src, dst);
	   return OK;  /* так как файл уже есть - считаем это удачей */
	}
	/* если получатель уже существует, то
	 * запросить подтверждение на перезапись */
	if(*confirm && typeto.type == S_IFREG){
	   char answer[40];
	   printf("%s already exists. Overwrite (y/n/all) ? ", dst);
	   fflush(stdout);
	   switch( *gets(answer)){
	   case 'n': default:  return OK; /* ничего не делать */
	   case 'y':           break;
	   case 'a': *confirm = NO; /* дальше - без запросов */
		     break;
	   }
	}
	return cp ? docopy(src, dst, typefrom.type, typeto.type) :
		    domove(src, dst, typefrom.type, typeto.type) ;
}
void main(int argc, char *argv[]) {
	char *cmd; int cp, i, err, confirm = YES;
	struct ftype typeto;  /* тип файла-получателя */
	if( argc < 3 ) {
		printf( "Usage: %s source... destination\n", argv[0] );
		exit(1);
		/* ненулевой код возврата сигнализирует об ошибке */
	}
	/* выделяем базовое имя программы. */
	cmd = basename( argv[0] );
	if     ( !strcmp( cmd, CP )) cp = 1;
	else if( !strcmp( cmd, MV )) cp = 0;
	else{
		printf( "%s - wrong program name.\n", cmd );
		exit(2);
	}
	typeto = filetype( argv[argc-1] );
	if(cp && typeto.type != S_IFDIR && typeto.type != S_IFBLK
	      && typeto.type != S_IFCHR && argc > 3){
		printf("Group of files can be copied "
		       "to the directory or device only.\n"); exit(3);
	}
	if(!cp && typeto.type != S_IFDIR && argc > 3){
		printf("Group of files can be moved "
		       "to the directory only.\n");           exit(4);
	}
	for(err=0, i=1; i < argc-1; i++)
		err += ! docpmv(argv[i], argv[argc-1], typeto,
				cp, &confirm);
	exit(err);  /* 0, если не было ошибок */
}

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед