LINUX.ORG.RU

Ошибка сегментирования. Рекурсивный обход каталогов

 


0

2

Помогите пожалуйста не могу найти где выходит за область памяти.

/*
#include "/usr/include/dirent.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include "/usr/include/sys/types.h"
#include "/usr/include/sys/stat.h"

int dir_info(char * dirpath);

int main(int argc, char * argv[]) {
  errno = 0;
  dir_info(argv[0]);
  return 0;
}

int is_executable(struct dirent *e) {
  struct stat st;
  lstat(e->d_name, &st);
  if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
    return (int)st.st_size;
  return 0;
}

int dir_info(char * dirpath) {

  DIR * dir = opendir(dirpath);
  errno = 0;
  if (dir) {
    perror("opendir ");
    return -1;
  };
  struct dirent* current = readdir(dir);
  while (current != NULL) {

    if (current->d_type == DT_DIR) {
      char * name_dir = current->d_name;
      int file_count = 0;
      int file_executable_count = 0;
      int files_size = 0;
      int filesize = 0;
      DIR * dirp;
      struct dirent * entry;
      dirp = opendir(name_dir);
      while ((entry = readdir(dirp)) != NULL) {
        if (entry->d_type == DT_REG) {
          file_count++;
          if ((filesize = is_executable(entry)) != 0)
            files_size += filesize;
          file_executable_count++;
        }
      }
      closedir(dirp);
      //fprintf("dir_name: %d - count_files: %d - count executable files: %d - size files: %d",
       // name_dir , file_count, file_executable_count, files_size);
      printf(name_dir);
    }
    printf("Hell");
  }

  return 0;

}

dir_info(argv[0]);

Тут скорее всего нужно передавать argv[1], заранее проверив, что argc >= 2.

DIR * dir = opendir(dirpath);
errno = 0;
if (dir) {
  perror("opendir ");
  return -1;
}

opendir возвращает NULL, если произошла ошибка, а она произошла, т.к. передается путь к исполняемому файлу (argv[0]) вместо директории для сканирования. Если dir == NULL (или if(!dir) - как заметил товарищ выше), тогда произошла ошибка, но ты о ней не узнаешь, потому что обнуляешь errno (зачем-то).

struct dirent* current = readdir(dir);
while (current != NULL) {

И дальше current не меняется, break'ов и return'ов из цикла нет, в результате - бесконечный цикл.

struct dirent* current = readdir(dir);
...
char * name_dir = current->d_name;
....
printf(name_dir);

Это называется format string attack ---> https://www.owasp.org/index.php/Format_string_attack

В контексте хелоуворлда - не особо важно, но лучше к этому не привыкать, поэтому рекомендую заменить printf() на puts(), либо использовать printf аккуратнее.

Nietzsche ()

Помогите пожалуйста не могу найти где выходит за область памяти.

Классическая ошибка ньюфага: в каждой директории у тебя будут "." и "..", которые нужно игнорировать, а у тебя выходит бесконечная рекурсия.

kawaii_neko ★★ ()

Дополнения к комментариям выше:
- ваш алгоритм не осуществляет рекурсивный обход каталогов
- opendir(name_dir) работает не так, как вы думаете

Sorcerer ★★★★★ ()
#include "/usr/include/dirent.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include "/usr/include/sys/types.h"
#include "/usr/include/sys/stat.h"

int dir_info(char * dirpath);

int main(int argc, char * argv[]) {
  errno = 0;         //Зачем errno в ноль сбросил?
  dir_info(argv[0]); //argv[0] - путь к твоему шедевру как ты его набрал в командной строке.
                     //Аргументы начинаются с argv[1].
  //printf("%s\n", basename(argv[0])); //Выведет имя твоего шедевра
  return 0;
}

int is_executable(struct dirent *e) {
  struct stat st;
  lstat(e->d_name, &st);
  if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
    return (int)st.st_size;
  return 0;
}

int dir_info(char * dirpath) {

  DIR * dir = opendir(dirpath);
  errno = 0; //Молодец! Тебе opendir устанавливает errno, а ты его тут же сбрасываешь.
  if (dir) { //opendir при ошибке возвращает NULL.
    perror("opendir "); //Что ты тут хочешь увидеть? Ты же сбросил errno сразу после вызова opendir!
    return -1;
  };
  struct dirent* current = readdir(dir);
  while (current != NULL) {

    if (current->d_type == DT_DIR) { //как гласит man всеведущий, d_type поддерживается не всеми файловыми системами
//Дальше трэшъ, угаръ и никакой рекурсии.
      char * name_dir = current->d_name;
      int file_count = 0;
      int file_executable_count = 0;
      int files_size = 0;
      int filesize = 0;
      DIR * dirp;
      struct dirent * entry;
      dirp = opendir(name_dir); //Почему выше, вызывая opendir, ты делаешь проверку на ошибку, а здесь - нет?
      while ((entry = readdir(dirp)) != NULL) {
        if (entry->d_type == DT_REG) {
          file_count++;
          if ((filesize = is_executable(entry)) != 0)
            files_size += filesize;
          file_executable_count++;
        }
      }
      closedir(dirp);
      //fprintf("dir_name: %d - count_files: %d - count executable files: %d - size files: %d",
       // name_dir , file_count, file_executable_count, files_size);
      printf(name_dir);
    }
    printf("Hell");
  }

  return 0;

}

Что примерно должно быть

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>

void usage(char* name) {
  printf("Usage: %s dir_name\n", name);
  printf("Имя папки надо указать, балбес!\n");
}

int dir_info(char * dirpath) {
  int err = 0;
  struct dirent current;
  struct dirent* result = NULL;
  DIR * dir = opendir(dirpath); //Будут проблемы с именами с пробелами. Это решай сам.

  if (!dir) {
    err = errno;
    printf("opendir %s: %s\n", dirpath, strerror(err));
    return err;
  };

  while (err = readdir_r(dir, &current, &result), !err && result) { //readdir использовать нельзя, догадайся сам почему.
    if(!strcmp(".", current.d_name) || !strcmp("..", current.d_name))
      continue;

    if (current.d_type == DT_DIR) { //как гласит man всеведущий, d_type поддерживается не всеми файловыми системами
                                     //Вероятно придётся использовать stat
      printf("dir: %s\n", current.d_name);
      err = dir_info(current.d_name); //Вот она, РЕКУРСИЯ!
      if(err) {
        //Обработка ошибки и, возможно выход, если она критическая
        goto on_err;
      }
    } else {
      printf("file: %s\n", current.d_name);
      //У нас файл, обрабатываем его
    }
  }
on_err:
  closedir(dir);
  return err;
}

int main(int argc, char * argv[]) {
  int err = 0;

  if(argc != 2) {
    usage(argv[0]);
    return 1;
  }

  err = dir_info(argv[1]);
  if(err)
    printf("%s\n", strerror(err));
  
  return err;
}

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

DIR * dir = opendir(dirpath); //Будут проблемы с именами с пробелами. Это решай сам.

Мдээ...

И вообще, не надо ТАК делать. Мало того, что сожрёте стек, так ещё и можно упереться в открытые дескрипторы. Правильно - запоминать в списке каталоги, а потом обрабатывать по одному из списка.

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

Мдээ...

Мдээ...

Limit                     Soft Limit           Hard Limit           Units     
Max stack size            8388608              unlimited            bytes

Какой глубины должна быть вложенность каталогов, чтобы засрать стек размером в 8 метров?

Не, там есть одна проблема, и она даже именно в приведённой тобой строке, но совсем не та, о которой ты говоришь. Две проблемы. Глупый комментарий и ещё одна, совсем не связанная с тем, о чём ты написал.

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

Какой глубины должна быть вложенность каталогов, чтобы засрать стек

Это неправильный вопрос. Вам никто не обещал, что: стек до вызова этой функции почти не заюзан, парсер конкрентного файла не будет сам жрать стек, стек будет таков (ведь при многопоточном исполнении он будет меньше) и так далее.

и ещё одна, совсем не связанная

Не юлите, этот комментарий выдаёт вас с головой, что вас допускать до системного программирования вообще нельзя.

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

Это неправильный вопрос. Вам никто не обещал, что

Что за чушь вы несёте. Вся задача ТС состоит в рекурсивном обходе каталогов и сборе информации о файлах. На основании этой задачи расскажите, кем и как может быть использован стек до вызова этой функции. И я так ничего не увидел о глубине вложенности каталогов, способной забить стек.

А ещё хотелось бы увидеть ваш вариант. А то балаболов вокруг много, а как до дела доходит, они сдуваются.

Не юлите, этот комментарий выдаёт вас с головой, что вас допускать до системного программирования вообще нельзя.

Не юлите, расскажите, что вы узнали из этого комментария. Очень интересно.

Да, и критерием допуска или не допуска меня к системному программированию является мнение работодателя, а своим ты можешь подтереться. Твоё мнение обо мне меня совершенно не интересует.

А вот о коде поговорить можно. Код давай. Тогда будет о чём с тобой поговорить.

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

А вот о коде поговорить можно. Код давай.

Умерьте пыл. Мне есть что показать, а балаболите тут именно вы. https://github.com/jgunthorpe/busybox/blob/master/scripts/bb_mkdep.c Конкретно обход каталогов начинается со строки 756, функция псевдорекурсивная, проста и не жрущая стек. А стек там у меня жрет парсер как не в себя.

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

если имя ЛЮБОЙ директории начинается с точки, то она игнорируется? (778 строка)

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

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

Умерьте пыл. Мне есть что показать

Тебе действительно есть что показать. Смотрим со строки 771.

...
dir = opendir(dirs->data);
if (dir == NULL)
    fprintf(stderr, "Warning: opendir(%s): %m\n", dirs->data);
dirlen = strlen(dirs->data);
while ((de = readdir(dir)) != NULL) {
...

Комментарии тут излишни...

О проблеме в строке

  DIR * dir = opendir(dirpath); //Будут проблемы с именами с пробелами. Это решай сам.
которая, на самом деле, вовсе не в пробелах, я уже и не спрашиваю. Ответа не будет, это очевидно.

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

Смотрим со строки 771.

Ну да, некорректно, так как зависит от реализации, возможно и не будет NULL в цикле, а упадёт. Вот только эта часть кода был совершенно побочной, голова была занята парсером и логикой. Где ж вы раньше были? Хотя и не надо. Посмотрел версию от 2006 года, там уже исправил. Вот что значить взять первый попавшийся URL со своим кодом, который и не контролируешь.

которая, на самом деле, вовсе не в пробелах,

Вот именно, в ДНК же.

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

Не юли. Обкакался

Вы разницу в забытом else, который потом появился в следующей версии после отладки основного функционала и утверждением, что opendir как-то не так обрабатывает пробелы понимаете? Этот хитрый код лишь маленький винтик в сложной системе сборки проекта. Да взять почти любую систему сборки и запретить каталог на сканирование запросто можно увидеть глюки. Вот они и были увидены и починены, 11 лет назад.

Я не сомневался в твоей некомпетентности.
И я сомневаюсь, что ты - автор того кода.

Пф, сливайтесь культурно, воняет же.

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

Сливаешься тут ты. Я уже выше написал, что мой комментарий - глупый и что в вызове opendir есть проблема, которая, как я думал, в именах с пробелами. Но я с этой проблемой разобрался за 5 минут. А ты уже 2 дня не можешь сказать, что не так в моём коде с вызовом opendir. Это и есть причина считать тебя некомпетентным, а вовсе не досадный баг с проверкой на ноль. И тем более, что ты, как ты говоришь, являешься автором кода, в котором используется opendir, и в котором этой проблемы нет. Но ты её назвать не можешь. Из этого я делаю вывод, что код, помеченный тобой, писал не ты. Ты его только пометил.

alexku ()