LINUX.ORG.RU

Нужен алгоритм гарантированного создания названий файлов

 ,


0

1

Всем привет!

Есть такая задача.

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

char *document_name = "diplomnaya";
int fd = openat(dirfd, document_name, O_RDWR | O_CREAT | O_EXCL, 0644); // -1, errno = EEXIST

Оказывается, что файл с таким именем уже есть.

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

Первое что приходит в голову: добавлять число в конце названия, постепенно его увеличивая если и такие названия будут заняты. Такой алгоритм используется, например, в современных браузерах.

Однако хочу заметить, что помимо прочего, существует ограничение на длину названия файла в 255 байт (NAME_MAX), как и в длину общего пути (PATH_MAX). И вполне реально «упасть» при попытке создания файла с подобным именем в errno == ENAMETOOLONG.

Какие есть предложения? Модифицировать оригинальное имя вплоть до наихудшего случая (полная неузнаваемость по сравнению с оригинальным названием) разрешается.

Выбирать имя файла пользователю не предлагать - он его видить не будет в 99% случаев, а совпадения по названию при сохранении иногда будут.

Стоит ли велосипедить что-то свое или достаточно просто взять https://man7.org/linux/man-pages/man3/tempnam.3.html ?

UPD: Решил сделать так: Нужен алгоритм гарантированного создания названий файлов (комментарий)
UPD2: Код:
int randfd = -1; // randfd = open("/dev/urandon", O_RDONLY);

#define RANDBYTES_WIDTH 11

void randfilename(char *ptr, size_t len) {
	const char pool[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	char randbytes[RANDBYTES_WIDTH];
	ssize_t got = read(randfd, randbytes, sizeof(randbytes));
	if (randfd < 0 or got < sizeof(randbytes)) { //fallback
		for (unsigned i = 0; i < sizeof(randbytes); i++) {
			randbytes[i] = (char) rand();
		}
	}

	for (unsigned i = 0; i < sizeof(randbytes); i++) {
		randbytes[i] = pool[randbytes[i] % (sizeof(pool) - 1)];
	}

	if (len + sizeof(randbytes) > NAME_MAX) len -= len + sizeof(randbytes) - NAME_MAX;
	memcpy(ptr + len, randbytes, sizeof(randbytes));
	ptr[len + sizeof(randbytes)] = '\0';
}

★★★★★

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

Есть более прошаренная версия рекомендованная к испльзованию вместо: mkstemp.

Проблема оригинального mktemp в race-condition между выбором имени файла и созданием оного (что тебе скорее всего без разницы).

В openbsd’шном мане об этом лучше прописано → https://man.openbsd.org/mktemp.3

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

с какого рожна

1. имя файла имеет длинну около 230 байт, например такое:

sdjasidsaidjasidjsaiodjioajdisoajixjxcjopifopieowpfiopdsifopdsifopidsopfdsiopfidsopfidvxcvpocxivopcxivopcxivopcxivopcxiopicopxviopvioxcpivopivpolkdjklfdjfkvnxcnvmxcvnmcxnvmcxnvmxcvmnvcxmncvmnvxcmxcvnmcvxnmxcvnmcxvnmcnvaadsxccxzct

и ты предлагаешь сделать вот так, да?
sdjasidsaidjasidjsaiodjioajdisoajixjxcjopifopieowpfiopdsifopdsifopidsopfdsiopfidsopfidvxcvpocxivopcxivopcxivopcxivopcxiopicopxviopvioxcpivopivpolkdjklfdjfkvnxcnvmxcvnmcxnvmcxnvmxcvmnvcxmncvmnvxcmxcvnmcvxnmxcvnmcxvnmcnvaadsxccxzctba33d417-37cb-45c4-86cf-76382436dfed


2. Имя файла короткое, но какой-то мудак (или нерадивый админ) засунул его в каталоги так глубоко, что даже при попытке создать файл, имя которого будет < 255, итоговый путь будет больше 4096

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

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

POSIX не определяет где этот файл находится, но glibc, например, его размещает в /tmp

Мне нужен файл по конкретному адресу который будет существовать всегда а не временно.

Проблема оригинального mktemp в race-condition между выбором имени файла и созданием оного (что тебе скорее всего без разницы).

Есть разница, но не вижу проблем в ее решении - проверить есть ли файл с нужным именем и тут же его создать если нет вполне реально сделать за один системный вызов (см. шапку темы). Вопрос лишь в выборе имени файла.

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

Использовать форму записи 8.3

Что это? Попытался загуглить - там что-то про 1С

hex число порядковое в конце

Какое именно? А если имя файла в результате будет больше NAME_MAX? Нужно сокращать длину оригинального файла чтобы влезть в NAME_MAX

reprimand ★★★★★
() автор топика

В общем, принял решение сделать так:

1. Уже при запуске программы проверять в каком каталоге работаем. Если в нем невозможно создать файл максимально длины, то есть итоговый путь выше чем PATH_MAX - сразу посылать
2. Генерировать некую короткую форму uuid в виде строки
3. Если имя файла + uuid в виде строки занимают места меньше чем NAME_MAX - просто дописывать
4. Иначе сокращать длину названия до тех пор пока не будет влазить строка с этим uuid

Код позже выложу

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

понял суть проблемы: сеньёр не осилил VCS и что-то странное мутит на коленке :-)

uuid для идентификации конкретного экземпляра достаточно. Если имя файла кажется чрезмерно длинно - отрежте лишнее,можно даже всё, добавьте uuid, имя всё одно получится уникальным. (Или вам выпадет исключительный шанс войти в историю и скомпрометировать стандарт)

а вообще велосипед изобретать вредно - берите FS с версионностью или отдельно контроль версий. Всякие «мой диплом, вариант 000223.вчера правил.мелко по нетрезв.doc» злое зло

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

сеньёр не осилил VCS

А кто-то имеет привычку недооценивать собеседника. Если вдруг собеседник окажется умнее тебя то иногда случается «ой».

Мне не нужно интегрировать целую VCS в свой софт, это излишне толсто для алгоритма на 5-10 строк который решает узкоспециализированную и ОДНУ задачу.

что-то странное мутит на коленке

Настолько странное что аж SVr4, 4.3BSD, POSIX.1-2001 для этого в свое время стандартизировали целую функцию в libc, ооок.

uuid для идентификации конкретного экземпляра достаточно

Да. А я хочу чтобы при открытии каталога с файлами были не сотни тысяч файлов с uuid, которые не несут НИКАКОЙ информации пока ты их не откроешь, а

readable_filename_with_cute_cats_0f7f33a0-9707

намного читабельнее

Если имя файла кажется чрезмерно длинно - отрежте лишнее,можно даже всё, добавьте uuid, имя всё одно получится уникальным

Уже так и решил сделать

а вообще велосипед изобретать вредно - берите FS с версионностью или отдельно контроль версий

Для моей задачи слишком overkill

Всякие «мой диплом, вариант 000223.вчера правил.мелко по нетрезв.doc» злое зло

У меня человек не будет руками править название файла. Никогда. Точка.

reprimand ★★★★★
() автор топика
Последнее исправление: reprimand (всего исправлений: 2)

Я тупо смотрел список файлов и при нахождениии повторов добавлял к имени число, типа name, name (1), name (2). Число увеличивакется, пока не найдется уникальный вариант. Работает 100% и при отчистке временных файлов длина имени будет близка к изначальной

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

Время к имени добавь текущее в секундах + 10 символов. И ничего мудрить не надо, лимиты на длинну проверил и всё. Я ниже прочитал что у тебя путь до каталога огромный. Но ты даже не сказал сколько свободных символов до предела 4096 остаётся. Сделай симлинк каталога с коротким путём и всё, да костыль, но если у тебя там пути по 4000+ символов до файла, то это не костыль, а элегантное решение (тока я не знаю есть ли нюансы)

LINUX-ORG-RU ★★★★★
()

Почему на си?

вполне реально «упасть»

Нет. Если не веришь, то попробуй так упасть и оцени реальность такого юзкейса.

Суть в том, что в конец имени добавляется число в десятичной системе. Сколько знаков оно будет занимать? 3? 5? 10? Ещё хорошо бы добавить символ подчеркивания, чтобы отделить от имени файла (пробелы - зло). То есть ты добавляешь 11 символов. Если у кого то имя файла превышает 240 то пусть идёт лесом.

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

Почему на си?

Потому что я пишу, писал, и буду писать софт на си. Почему именно я это делаю - тема отдельного сообщения.

Если не веришь, то попробуй так упасть и оцени реальность такого юзкейса

А мне не надо пробовать - есть любители-админы, создающие изолированные окружения (у которых основной механизм - chroot() кстати) где-то в глубине своих супертолстых серверов. За свою жизнь я раза 4 упирался в лимит PATH_MAX, и огромное кол-во раз в NAME_MAX

Суть в том, что в конец имени добавляется число в десятичной системе

Тему не читай & сразу отвечай - девиз ЛОР-а с эпохи его рождения!

Если у кого то имя файла превышает 240 то пусть идёт лесом

Как удобно ты опустил утверждение о том 240 ЧЕГО ИМЕННО. 240 БАЙТ, КАРЛ!

Ну да, пусть пользователи utf-8 с 3-4 байтами на символ идут лесом, ну а чо? Никогда не видел азиатские документы с смешанным названием (иероглифы + латиница)? Если ты с чем-то не сталкивался - это не значит значит что этого нет, и уж тем более я лично не буду посылать лесом моих пользователей. Дьявол кроется в деталях.

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

Что это? Попытался загуглить - там что-то про 1С

Простите, что?

https://www.google.com/search?q=8.3+filename+format&oq=8.3+filename+format&aqs=chrome..69i57.4818j0j7&sourceid=chrome&ie=UTF-8

An 8.3 filename (also called a short filename or SFN) is a filename convention used by old versions of DOS and versions of Microsoft Windows prior to Windows 95 and Windows NT 3.5. It is also used in modern Microsoft operating systems as an alternate filename to the long filename for compatibility with legacy programs.

https://en.wikipedia.org/wiki/8.3_filename

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

Тему не читай & сразу отвечай

отсылка к какому-то конкретному сообщению?

240 БАЙТ

а чего еще? точно что не килобайт :)

utf-8

и что с того?

предположим пользователь назвал файл в utf-8 по два байта на символ. если добавить в конец _9 то выходит я отнял символ у пользователя. это много? отняв два символа я могу написать _999. uuid кажется занимает 128 бит, тогда как строка _999 занимает 32 бита.

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

а чего еще?

Судя по «знаков» или «символов» о которых ты говорил ранее было похоже что ты не понимаешь разинцу

предположим пользователь назвал файл в utf-8 по два байта на символ

Я тебе уже назвал реальный сценарий при котором ты упираешься в NAME_MAX или PATH_MAX, если ты в упор не видишь его и аргументируешь это другим сценарием, в котором «все чикипуки», то смысл этого действа? У тебя все хорошо - проходи дальше, а там где есть проблема я уже нашел решение, спасибо

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

Lorem_ipsum_dolor_sit_amet,consectetur_adipiscing_elit,sed_do_eiusmod_tempor

Lorem_ipsum_dolor_sit_amet,consectet_57dbafcc-d0a5-11ec-9d64-0242ac120002

Lorem_ipsum_dolor_sit_amet,consectetur_adipiscing_elit,sed_do_eius_999999

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

Обрезай имя файла до допустимой длины и добавляй UUID в конец. Расширение можно перенести после UUID (если слишком длинное, тоже обрезать).

KivApple ★★★★★
()

Первое что приходит в голову: добавлять число в конце названия, постепенно его увеличивая если и такие названия будут заняты. Такой алгоритм используется, например, в современных браузерах.

Однако хочу заметить, что помимо прочего, существует ограничение на длину названия файла в 255 байт (NAME_MAX), как и в длину общего пути (PATH_MAX). И вполне реально «упасть» при попытке создания файла с подобным именем в errno == ENAMETOOLONG.

Ну так подрезай, чтобы не падало, тоже мне проблема.

long-long-filename-9 
long-long-finenam-10

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

Я думаю генерация 5-значного случайного числа даст гарантию близкую к 100% для одного пользователя. Имя+число

no2700
()

зачем гуиды то??? dp + время создания файла в секундах с начала эпохи(можно в шестадцатиричном виде)

будет типа dp66743297584.txt. и упорядочено по дате создания.

если что, в году - 31556926 секунд

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)

randbytes[i] = pool[randbytes[i] % (sizeof(pool) - 1)];

с какой стати sizeof(pool)-1 ? просто по модулю от размера массива.

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

с какой стати sizeof(pool)-1 ?

Расскажи что выведет программа не запуская ее на пк:

#include <stdio.h>

int main() {
    char str[] = "abc";
    printf("%zu\n", sizeof(str));
    return 0;
}

reprimand ★★★★★
() автор топика

Винда 95 же. Diplom~1.doc, … Diplo~10.doc

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