4.6   Визначення й встановлення параметрів файлу

Ряд функцій Turbo С використовується для одержання і зміни параметрів файлу. Якщо файл не відкритий, інформація про нього може бути отримана тільки з елемента директорія (див. 2.3). Якщо ж файл відкритий, доступна додаткова інформація: положення покажчика запису-читання, права доступу до файлу, задані при відкритті або створенні файлу. Як відзначалося в 2.7, MS-DOS веде внутрішню таблицю для кожного відкритого файлу. Turbo С має спеціальні засоби доступу до цієї інформації з прикладної програми. Крім того, якщо файл відкритий як потік, програма може використовувати інформацію, записану в структурну змінну по шаблону FILE.

Корисна інформація про файл може бути отримана мобільними функціями fstat() і staf(). Обидві ці функції заповнюють поля структурної змінної по шаблону struct stat, опис якого приведено в заголовному файлі <sys\stat.h>:

struct stat {
  short st_dev;              
/* номер накопичувача або присрою */
  short st_ino;               
/* у MS-DOS не використовується;
                                      в ОС UNIX - номер i-вершини (i-node) */

  short st_mode;           
/* біти прав доступу до файла */
  short st_nlink;            
/* число ”прив’язок” до файлу: в MS-DOS дорівнює 1;
                                      в ОС UNIX задає число операцій відкриття файлу
                                      рівнобіжними процесами */

  int st_uid;                   
/* в MS-DOS не використовується;
                                      в ОС UNIX - ідентифікатор володаря файлу */

  int st_gid;                   
/* в MS-DOS не використовується;
                                      в ОС UNIX - ідентифікатор групи володарів файлу */

  short st_rdev;            
/* те ж, що і st_dev */
  long st_size;               
/* розмір відкритого файлу */
  long st_atime;            
/* в MS-DOS - час останнього відновлення;
                                      в ОС UNIX-час останнього доступу до файлу */

  long st_mtime;           
/* в MS-DOS співпадає з  st_atime;
                                      в ОС UNIX - час останньої модифікації */

  long st_ctime;             
/* в MS-DOS збігається з st_atime;
                                      в ОС UNIX - час створення файлу */

};

Далі приводиться специфікація функцій.

#include <sys\stat.h>
int fstat( int handle, struct stat *statbuf )

Записує інформацію про відкритий файл у структурну змінну, на яку вказує statbuf. Функція, що викликає, повинна подбати про резервування пам’яті для інформації що повертається. Відкритий файл задається префіксом handle. У випадку успіху повертається 0; у протилежному випадку повертається -1 і код помилки MS-DOS записується в зовнішню змінну errno. Звичайно це значення ENOENT, що говорить про те, що заданий файл або директорій не існує.

#include <sys\stat.h>
int stat( char *path, struct stat *statbuf )

Усе те ж, що і для fstat, але файл задається ASCIIZ-рядком специфікації, на який вказує path. Функція не потребує попереднього відкриття файлу.

Найбільше часто звернення до функцій fstat() або stat() при роботі в MS-DOS виконується, якщо необхідно визначити розмір файлу або права доступу до нього. Наступний фрагмент програмного коду ілюструє використання fstat() для визначення розміру файлу:

#include <sys\stat.h>
int main() {
  FILE *stream;
  long file_size;
  /* опис структурної змінної, а не покажчика на неї */
  struct stat my_stat;
 
  if( (stream = fopen( “c:\\tc\\my_file.txt”, “r” ) ) != NULL ) {
     fstat( fileno( handle ), &my_stat );
     file_size = my_stat.st_size;
    
  }
  else {
     /* Опрацювання помилки відкриття файлу */
    
  }
}

Недолік наведеного засобу складається в необхідності опису структурної змінної my_stat, що займає 30 байт, тільки для того, щоб одержати 4 байти корисної інформації. Існує інший засіб визначення довжини файлу, більш кращий у тих випадках, коли здаються жорсткі вимоги до розміру програми і не дуже суворі по продуктивності. Цей засіб заснований на встановленні покажчика запису-читання на кінець файлу і одержанні поточної позиції покажчика. Після цього початкова позиція покажчика запису-читання файлу повинна бути відновлена.

/* Ілюстрація визначення розміру відкритого файла */
#include <stdio.h>
int main( ) {
  FILE *stream;
  long file_size;
  fpos_t file_ptr; /* покажчик запису-читання файлу;
      тип fpos_t описаний у stdio.h як long */
 
  if( ( stream=fopen( “c:\\tc\\my_file.txt”, “r” ) ) != NULL ) {
     fgetpos( stream, &file_ptr );    /* зберігання покажчика */
     fseek( stream, 0L, SEEK_END );           /* встановлення на EOF */
     fgetpos( stream, &file_size );  /* одержання довжини файлe */
     fsetpos( stream, &file_ptr ); /* відновлення позиції
      покажчика запису-читання */
    
  }
  else {
     /* Опрацювання помилки відкриття файлу */
    
  }
}

Наведена програма ще більш скоротиться, якщо файл буде відразу відчинятися для поповнення (із правами “а” або “а+”). У цьому випадку відпадає необхідність “примусового” встановлення покажчика на кінець файлу, а також зберігання і відновлення покажчика.

Покажемо, як можуть бути використані інші поля структурної змінної по шаблону stat, що повертається функціями stat() та fstat().

Дуже часто при роботі з файловою системою виникає задача визначення типу того або іншого файлу. Далі наводиться програма, що видає на екран інформацію про тип файлу, специфікація якого задана першим параметром командного рядка при запуску програми.

/*L_7.C*/
#include <stdio.h>
#include <stat.h>
int main(int argc, char **argv) {
  struct staf work;
  if( argc < 2) { /* чи задані параметри в командному рядку */
     printf(“\аВикористання програми %s <ім’я_файлу>\п”, argv[0]);
     retun( 1 );
  }
  if( stat( argv[ 1 ], &work ) ) {
     perror( “\a”);
     return( 255 );
  }
  switch( ( work.st_mode & S_IFMT) {
     case S_IFREG : fputs( “Регулярний файл”, stdout ); break;
     case S_IFDIR : fputs( “Директорій”, stdout ); break;
     case S_IFCHR : fputs( “Символьний спеціальний файл”, stdout);
      break;
  }
  if( work.st_mode & S_IREAD) fputs(“Припускає читання”, stdout);
  if( work.st_mode & S_IWRITE) fputs(“Припускає запис”, stdout);
  putchar(‘\n’);
  return( 0 );
}

Програма, крім чисто навчальних цілей, дозволяє визначити імена спеціальних символьних файлів, підтримуваних ОС. Для цього при запуску програми варто задавати ім’я спеціального файлу (див. 2.1). Якщо функція stat() повертає при своєму виклику -1, це ознака того, що даний спеціальний файл не підтримується комп’ютером. Попрацював з програмою, можна  переконатися в тому, що символи ‘.’ і ‘..’ є припустимими при завданні маршрутів директорія і відповідають поточному і батьківському директоріям. Проте є обмеження на їхнє використання.

Як приклад роботи програми далі наведена копія екрану при запуску програми на виконання з різними аргументами.

G:\>DIR G: - виведення вмісту кореневого директорія

Volume in drive G is MS-RAMDRIVE
Directory of G:\
COMMAND           СОМ       47987         04-12-91       5:10a
GL2_29                  OBJ         1472           07-30-91       2:46p
GL2_29                  EXE         12236         07-30-91       2:46p
D                             <DIR>                       07-30-91       3:24p
                                      4file(s)                 61695 bytes
                                                                  980480 bytes free

G:\>L4_7 COMMAND.COM – запит інформації про файл COMMAND.COM
Регулярний файл. Припускає читання. Припускає запис.

G:\>L4_7 G:\D - запит інформації про файл \D (директорій)
Директорій. Припускає читання.

G:\>L4_7 CON - запит інформації про спеціальний файл CON
Символьний спеціальний файл. Припускає читання. Припускає запис

G:\>L4_7 .
: No such file or directory

Помилка при останньому запуску програми на виконання виникає через те, що символи ‘.’ і ‘..’ є елементами будь якого директорія, крім кореневого. Змінимо поточних директорій і знову запустимо програму.

G:\>CD D - встановлення поточна директорія G:\D
G:\D>DIR – виведення вмісту директорія G:\D

Volume in drive G is MS-RAMDRIVE
Directory of C:\D
                      <DIR>       07-30-91       3:24p
 ..                   <DIR>       07-30-91       3:24p
 E                   <DIR>       07-30-91       3:24p
                      3 File(s)     479744 bytes free

G:\D>L4_7 .
Директорій. Припускає читання.

Функції stat, fstat відповідають стандарту ANSI мови програмування Сі, і їх застосування забезпечує мобільність програми.

Turbo С має спеціальну немобільну функцію визначення довжини вже відкритого файлу filelenglh().

#include <io.h>
long filelength ( int handle )

Повертає довжину в байтах відкритого файлу, на який посилається префікс файлу handle. Для одержання довжини файлу, відкритого як потік, необхідно використовувати макро fileno() для визначення префікса файлу. У випадку помилки повертається -1 і зовнішня змінна errno встановлюється в значення EBADF.

Довжина файлу, відкритого для префіксного доступу, може бути змінена функцією Turbo С chsize().

#include <io.h>
int chsize( int handle, long size )

Усікає або розширює файл, на який посилається префікс handle, до нового розміру, заданого параметром size. При розширенні файлу у файл добавляються нульові байти. При усіканні файлу всі дані праворуч нового кінця файлу губляться. У випадку успіху повертається 0. У протилежному випадку повертається -1 і код помилки записується в зовнішню змінну errno. Якщо не залишилося достатньо місця на диску для розширення файлу, errno встановлюється в значення ENOSPC. Спроба змінити розмір файлу, що має атрибут “тільки для читання”, або посилання на невідкритий файл призводять до того, що errno дорівнює EBADF. У тому випадку, якщо використовується поділ файлів і файл блокований для запису, errno буде дорівнює EACCES.

Далі наведемо приклад програми, що запитує ім’я файлу і новий розмір і виконує його зміну:

/*L4_8.C*/
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <io.h>
int main( void ) {
  int handle;
  long unsigned new_size;
  char pathname [ 128 ], prompt;
  do {
     fpuls( “Введіть ім’я файлу:”, stdout);
     fflush( stdin ); fgets( pathname, 128, stdin );
     pathname[strlen( pathname ) – 1 ]=‘\0’;
     if(!access( pathname, 0 ) )
        if(!access( pathname, 2 ) ) {
           if( ( handle = open( pathname, 0_WRONLY ) ) != -1 ) {
              printf( “Довжина файлу %lu.”, filelength( handle ) );
              fputs( “Новий розмір файлу:”, stdout );
              fflush( stdin ); scanf( “%lu”, &new_size );
              if( chsize( handle, new_size ) ) {
                 switch( errno ) {
                    case ENOSPC :
                       puts( “\a Нема простору на диску” ); break;
                    case EACCES :
                       puts( “\a Файл блоковано від запису” ); break;
                 }
              }
           }
           else perror( “\a Помилка відкриття файлу” )ж
        }
        else puts( “chsize() не змінює розмір файла для читання.” );
     else puts( “Задано файл, який нейснує” );
     fputs( “Продовжити? (Y/N)”, stdout );
     fflush( stdin );
     close( handle );
  }
  while( ( prompt = toupper( getchar() ) ) == ‘Y’ );
  return( 0 );
}

Три функції Turbo С використовуються для зміни прав доступу до файлу - chmod(), _chmode() setmode().

#include <sys/stat.h>
int chmod( const char *filename, int amode );

Змінює права доступу до файлу, збережені файловою системою. У середовищі MS-DOS це означає зміну байта атрибута файлу. Параметр filename вказує на ASCIIZ-рядок специфікації файлу. Параметр amode утворений комбінацією символічних констант, наведених в Табл. 8. Зокрема S_IWRITE призводить до встановлення атрибута “архівний”, що дозволяє як читання, так і запис у файл. Значення amode, рівне S_IREAD, призводить до створення файлу що тільки читається. При роботі під керуванням MS-DOS неможливо задати атрибут файлу “тільки для запису”, тому комбінація S_IREAD | S_IWRITE аналогічна S_IWRITE. Зміна байта атрибута призводить до автоматичного закриття файлу, якщо filename посилається на відкритий файл. Якщо зміна прав доступу завершилася успішно, функція повертає 0. У протилежному випадку повертається -1 і в зовнішню змінну errno записується код помилки. Якщо filename задає неіснуючий файл то errno=ENOENT. Якщо порушені права доступу до файлу, errno=EACCES. Зокрема, таке значення повертається при спробі встановити права доступу до директорію.

#include <io.h>
int _chmod( const char *filename, int func, [, int attrib ] );

Специфічна для MS-DOS форма зміни прав доступу до файлу. Дозволяє або запросити, або встановити права доступу до файлу. Параметр filename указує на ASClIZ-рядок специфікації файлу. Якщо параметр func дорівнює 0, функція повертає поточні атрибути файлу й у цьому випадку третій параметр ігнорується. Якщо func=l, установлюються права доступу до файлу, що задаються параметром attrib. Він формується операцією порозрядного АБО символічних констант, наведених у Табл. 9. Зміна байта атрибута призводить до автоматичного закриття файлу, якщо filename посилається на відкритий файл. Якщо зміна прав доступу завершилося успішно, функцій повертає 0. У противному випадку повертається -1 і в зовнішню змінну errno записується код помилки. Якщо filename задає неіснуючий файл, то errno=ENOENT. Якщо порушені права доступу до файлу, errno=EACCES. Зокрема, таке значення повертається при спробі встановити права доступу до директорію.

#include <io.h>
int setmode( int handle, int amode )

Встановлює режим доступу до відкритого файлу, на який посилається handle, в значення, що задається параметром amode. Параметр amode дорівнює або 0_TEXT, задаючи цим трансляцію комбінації символів CR LF (див. 3.1). або 0_BINARY, що задає відсутність трансляції символів. Функція особливо корисна для установки потрібного режиму для драйверів символьних пристроїв (спеціальних символьних файлів), що відповідають файлам стандартного введення-виведення. По замовчанню ці файли працюють у текстовому режимі. Для регулярних файлів потрібний режим доступу може задаватися або при відкритті, або через зовнішню змінну _fmode. При відсутності помилок функція повертає 0. У протилежному випадку повертається -1 в зовнішню змінну errno записується код помилки. Код помилки дорівнює EBADF, якщо handle посилається на невідкритий файл, і EINVAL, якщо параметр amode не дорівнює 0_TEXT або 0_BINARY.

Функції зміни прав доступу до файлу завжди будуть зазнавати невдачі, якщо ім’я файлу посилається на неіснуючий елемент директорія, директорій або мітку тому.

І останнє питання, аналізований у даному розділі, ‑ це інтерпретація й встановлення дати і часу файлу. Час і дату створення (модифікації) файлу повертають функції пошуку findfirst() і findnext() (див. 4.3), а також функції stat() і fstat(), розглянуті раніше. Проте ця інформація зберігається в окремих бітових полях відповідно за прийнятого в MS-DOS схемою (див. 2.3). Для полегшення роботи з полями дати і часу використовується структура по шаблону ftime, що описана в заголовному файлі <io.h>:

struct ftime {
  /* Час створення або модифікації файла (див. 2.3). */
  unsigned  ft_tsec : 5;                         /* число 2-секундних одиниць */
  unsigned  ft_min : 6;                         /* число хвилин (0-59) */
  unsigned  ft_hour : 5;                       /* число годин (0-23) */
  /* Дата створення або модифікації файла. */
  unsigned  ft_day : 5:                         /* день (1-31) */
  unsigned  ft_month : 4;                     /* місяць (1-12) */
  unsigned  ft_year : 7;                        /* рік (0-119, відповідає рокам
                                                            з 1980 по 2099) */
};

Приведений шаблон полегшує роботу з виділення окремих полів дати або часу файлу.

Є декілька можливостей одержати дату і час файлу. По-перше, дату і час файлу повертають функції пошуку findfirst() і findnext(). Проте для того, щоб використовувати шаблон ftime з цими функціями, необхідно виконувати деякі додаткові дії (див. далі приклад функції print_element()).

Як приклад наведемо перероблений варіант внутрішньої функції print_element() (див. L4_2. 4.3), спроможної додатково виводити дату і час файла.

/* Внутрішня функція виведення  елемента директорія.
Використовується в програмі L4_2.C.*/
#include <io. h>
void print_element( struct ffblk *my) {
  union {
     long unsigned time; /* старше слово-дата. молодше-час */
     struct ftime work;
  } r;
  char buf[ 11 ], *ptr = buf;         /* використовується функцією sprintf */

  /* Заповнення полів структурної змінної work */
  r.time = ( ( ( long unsigned )my->ff_fdate ) << 16 ) | my->ff_ftime;
  printf( “%-12s%9lu ”, my->ff_name, my->ff_fsize );

  /* Видача дати файла у форматі “день-місяць-рік” */
  sprintf( ptr, “%02u : ”, r.work.ft_day ); ptr += strlen( ptr );
  sprintf( ptr, “%02u : ”, r.work.ft_month ); ptr += strlen( ptr );
  sprintf( ptr, “%02u : ”, r.work.ft_year + 1980);
  printf( “%s ”, buf );

  /* Видача часу файла у форматі “години-хвилини-секунди”. */
  ptr = buf; sprintf( ptr, “%02u - ”, r.work.ft_hour );
  ptr += strlen( ptr ); sprintf( ptr, “%02u - ”, r.work.ft_min);
  ptr += strlen( ptr ); sprintf( ptr, “%02u - ”, r.work.ft_tsec * 2);
  printf( “%s”, buf );

  if( my->ff_attrib & FA_LABEL ) printf( “%s”, “Мітка тому” );
  if( my->ff_attrib & FA_DIREC ) printf( “%s”, “Директорій” );
  if( my->ff_attrib & FA_ARCH ) printf( “%s”, “Архівний” );
  if( my->ff_attrib & FA_RDONLY ) printf( “%s”, “Тільки читання” );
  if( my->ff_attrib & FA_SYSTEM ) printf( “%s”, “Системний” );
  if( my->ff_attrib & FA_HIDDEN ) printf( “%s”, “Прихований” );
  putchar( ‘\n’ );
}

При використанні функцій stat() і fstat() дата і час повертаються в одному полі типу long, що задає час у прийнятому в ОС UNIX форматі. Час задається кількістю секунд, минулих із моменту “створення світу” ОС UNIX - з 0 годин 0 хвилин 0 секунд часу Гринвичеського меридіану 1 січня 1970 року. Природно, що інтерпретація цього часу програмістам незручна і для цього в Turbo С є ряд сумісних з ОС UNIX функцій.

Для полегшення формування ASCIIZ-рядка, що надає час і дату файлу, використовується функція ctime().

#include <time.h>
char *ctime( const time_t *time )

Перетворює значення часу, на яке вказує time, у рядок символів такого виду:

Mon Nov 21 11:31:54 1998\n\0

Перше слово задає день тижня, друге - місяць, третє - час у прийнятому системою форматі, наприклад, години : хвилини : секунди. Потім задається рік і добавляються символи нового рядка і нуль-термінатор, функція повертає покажчик на початок сформованого рядка. Самий рядок зберігається у внутрішній статичній пам’яті і перевизначається кожним новим зверненням до функції.

Формати уявлення часу і дати, прийняті в MS-DOS і ОС UNIX, відрізняються. Спеціальні функції dostounix() і unixtodos() використовуються для перетворень форматів.

#include <dos.h>
long dostounix( struct date *d, struct time *t )

Перетворює дату і час із прийнятого в MS-DOS формату у формат ОС UNIX. Повертає число секунд, минулих із моменту “створення світу” ОС UNIX. MS-DOS-дата задається в структурній змінній по шаблону date, визначеному в <dos.h>:

struct date {
  int da_year;                          /* рік 0 -119, відповідає рокам 1980 - 2099 */
  char da_day;                        /* день (1-31) */
  char da_mon;                       /* місяць (1-12) */
};

Час, що повідомляється MS-DOS, задається в структурній змінній по шаблону time, визначеному в <dos.h>:

struct time {
  unsigned char  ti_min;                                   /* хвилини */
  unsigned char  ti_hour;                                 /* години */
  unsigned char  ti_hund;                                 /* соті долі секунди */
  unsigned char  ti_sec;                                    /* секунди */
};

 

#include <dos.h>
void unixtodos( long time, struct date *d, struct time *t )

Перетворює час time у форматі ОС UNIX у формат MS-DOS і повертає отриманий результат у структурні змінні, на які вказують d і t. Структурні змінні повинні бути описані в програмі, що викликає.

Нижче як приклад наводиться програма, що видає на екран інформацію про розмір, дату і час файлу, ім’я якого задано першим аргументом командного рядка.

/* L4_10.C */
#include <stdio.h>
#include <stat.h>
#include <time.h>
int main( int argc, char **argv ) {
  struct stat work;
  char *str;
  if( argc < 2) {
     printf( “\аВикористання програми %s <ім’я_файлу> \п”,
            argv[0] );
     return( 1 );
  }
  if( stat( argv[1], &work ) ) {
     perror(“\a”);
     return( 1 );
  }
  str = ctime( &work.st_mtime );
  printf(“Розмір: %9и байтів. Створений (модифікований):”,
              work.st_size);
  puts( str );
  return( 0 );
}

Ще один, мабуть, найбільше зручний для MS-DOS засіб одержання дати і часу файлу ‑ це використання функції getftime().

#include <io.h>
int getftime( int handle, struct ftime *ftimep )

Заповнює поля структурної змінної по шаблону ftime, на початок якої вказує ftimep, інформацією про дату і час створення (модифікації) відкритого файлу, на який посилається префікс handle. У випадку успіху функція повертає 0. У протилежному випадку повертається -1 і в зовнішню змінну errno записується код помилки (звичайно це значення EBADF).

Встановлення дати і часу у файлі виконує функція setftime().

#include<io.h>
int setftime( int handle, struct ftime *ftimep )

Встановлює дату і час відкритого файлу в значення, збережене в структурній змінній по шаблону ftime, на початок якої вказує ftimep. Відкритий файл ідентифікується префіксом handle. У випадку успіху функція повертає 0. У протилежному випадку повертається -1 і в зовнішню змінну errno записується код помилки (звичайно це значення EBADF).

Наведемо приклад програми, що встановлює нові дати і час для файлу, ім’я якого задається з клавіатури.

/* L4_11.C */
#include <sldio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <io.h>
int main( void ) {
  int handle;
  unsigned f_year, f_month, f_day, f_hour, f_minute, f_second;
  struct ftime old_date_time, new_date_time;
  char pathname[128], prompt;
  do {
     fputs( “Введіть ім’я файлу:”, stdout );
     fflush( stdin ); fgets( pathname, 128, stdin );
     pathname[ strlen( pathname ) – 1 ] = ‘\0’;
     if( ( handle = _open( pathname, 0_RDONLY ) ) != -1 ) {
        /* Визначення і друк дати і часу файла. */
        getftime( handle, &old_date_time );
        printf( “Дата %02и-%02и-%04и.”\
                  “Час: %02и:%02и:%02и.\п",
                   old_date_time.ft_day, old_date_time.ft_month,
                   old_date_time.ft_year + 1980,
                   old_date_time.ft_hour, old_date_time.ft_min,
                   old_date_time.ft_tsec * 2 );
        /* Запит нової дати і часу. */
        printf( “Введіть нову дату у форматі ДД-ММ-РР:” );
        fflush( stdin );
        scanf( “%u-%u-%u”, &f_day, &f_month, &f_year );
        printf( “Введіть новий час у форматі ГГ-ХХ-СС:” );
        fflush( stdin );
        scanf(“%u-%u-%u”. &f.hour, &f_minute, &f_second );
        /* Заповнення полів структури new_date_time. */
        new_date_time.ft_tsec = f_second / 2;
        new_date_time.ft_min = f_minute;
        new_date_time.ft_hour = f_hour;
        new_date_time.ft_day = f_day;
        new_date_time.ft_month = f_month;
        new_date_time.ft_year = f_year - 80;
        /* Установка нових дати і часу. */
        if( setftime( handle, &new_date_time ) )
           perror( “\аДата і час не змінені.” );
        close( handle );
     }
     else perror( “\а Помилка відкриття файлу. ” );
     fputs( “Продовжуєте? (Y/N) ”, stdout );
     fflush( stdin );
  }
  while( ( prompt = toupper( getchar( ) ) ) ==‘Y’ );
  return( 0 );
}

Наведені в даній главі функції дозволяють виконати майже усі виникаючі на практиці операції з файлами: відкриття і закриття, створення файлів і директоріїв, читання вмісту та т.п.