layout: непомещающееся целевое имя уводит задачу в review вместо failed
- предел длины компонента (255 байт) проверяется в BuildLinks до первой операции с ФС: ни каталога, ни ссылки при отказе не создаётся - причина пустого предпросмотра считается на показе (ReviewData.PreviewError) и печатается в панели действий и в карточке Telegram: у задачи без записанной причины взять её больше неоткуда
This commit is contained in:
@@ -6,6 +6,53 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// maxComponentBytes — предел длины одного компонента целевого пути в БАЙТАХ
|
||||
// UTF-8, а не в символах: ядро меряет NAME_MAX в байтах, и кириллическое
|
||||
// название упирается в предел вдвое раньше латинского той же длины в знаках.
|
||||
// Значение — NAME_MAX у ext4/xfs/btrfs; у ядра оно не выясняется, потому что
|
||||
// раскладка обязана отказать до обращения к диску (см. spec file-layout).
|
||||
// Цена обеих сторон: на ФС с меньшим пределом (часть зашифрованных) имя пройдёт
|
||||
// проверку и упрётся в ядро — останется сегодняшний failed; на ФС с бо́льшим мы
|
||||
// откажем строже, чем нужно. Лечение — правка этой константы, а не настройка:
|
||||
// значение, которое некому выставить осознанно, не гибкость.
|
||||
const maxComponentBytes = 255
|
||||
|
||||
// checkComponentLengths проверяет, что каждый компонент пути dst ПОД корнем
|
||||
// root помещается в maxComponentBytes. Корень не проверяется: его каталоги задаёт
|
||||
// оператор, и жаловаться на них раскладка не вправе. Возвращает ошибку,
|
||||
// обёртывающую ErrNameTooLong и называющую непомещающийся компонент и его длину.
|
||||
// Чистая функция: к диску не обращается.
|
||||
func checkComponentLengths(root, dst string) error {
|
||||
rel, err := filepath.Rel(filepath.Clean(root), filepath.Clean(dst))
|
||||
if err != nil {
|
||||
return fmt.Errorf("layout: relative target %q: %w", dst, err)
|
||||
}
|
||||
for c := range strings.SplitSeq(rel, string(filepath.Separator)) {
|
||||
if len(c) > maxComponentBytes {
|
||||
return fmt.Errorf("%w: %q — %d байт при пределе %d",
|
||||
ErrNameTooLong, shorten(c), len(c), maxComponentBytes)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// errNameSample — сколько рун непомещающегося имени показать в тексте ошибки.
|
||||
// Текст уезжает в error_msg, а оттуда в баннер ревью и в карточку Telegram:
|
||||
// имя целиком (а оно по условию длиннее 255 байт) заняло бы там весь экран.
|
||||
// Точную длину несёт число рядом, поэтому образца хватает, чтобы узнать имя.
|
||||
const errNameSample = 40
|
||||
|
||||
// shorten оставляет от имени начало и конец, выкидывая середину. Режет по рунам:
|
||||
// обрыв посреди многобайтовой буквы дал бы в сообщении мусор.
|
||||
func shorten(s string) string {
|
||||
r := []rune(s)
|
||||
if len(r) <= errNameSample {
|
||||
return s
|
||||
}
|
||||
head := errNameSample / 2
|
||||
return string(r[:head]) + "…" + string(r[len(r)-head:])
|
||||
}
|
||||
|
||||
// sanitizeComponent чистит один компонент пути (имя папки/файла): убирает
|
||||
// разделители, управляющие символы и неудобные для ФС/SMB знаки, схлопывает
|
||||
// пробелы и срезает точки/пробелы по краям. Кириллица и пробелы внутри
|
||||
|
||||
Reference in New Issue
Block a user