1 puntos por GN⁺ 3 시간 전 | 1 comentarios | Compartir por WhatsApp
  • En Linux 7.2 desaparecen los usos de la API strncpy dentro del kernel, y la interfaz de copia de cadenas, cuyo retiro estaba previsto desde hace tiempo, queda eliminada por completo
  • strncpy() copia la cantidad de bytes indicada, pero su comportamiento de terminación NUL no es intuitivo, por lo que durante años siguió siendo una fuente de errores en el kernel
  • Su característica de rellenar innecesariamente con 0 el búfer de destino incluso generaba problemas de rendimiento, y quitarla requirió cerca de 6 años y 362 commits
  • En el merge del viernes se eliminó no solo la API en sí, sino también la última implementación específica por arquitectura para per-CPU
  • El código del kernel ahora debe elegir funciones alternativas según el caso de uso, como strscpy(), strscpy_pad(), strtomem_pad(), memcpy_and_pad() y memcpy()

strncpy desaparece en Linux 7.2

  • Linux 7.2 elimina por completo la API strncpy, cuyo retiro dentro del kernel estaba previsto desde hace mucho tiempo
  • Tras 6 años de trabajo de limpieza, ya no queda código dentro del kernel que use la interfaz strncpy
  • Este cambio no fue solo un reemplazo de función, sino más bien una limpieza de prácticas antiguas de copia de cadenas en todo el kernel

La magnitud del trabajo hasta su eliminación

  • La eliminación de strncpy requirió unos 362 commits
  • El trabajo avanzó eliminando de forma gradual el código del kernel que usaba strncpy
  • En Linux 7.2 esta limpieza finalmente llegó a su punto de cierre

Por qué strncpy era un problema en el kernel

  • Durante años, strncpy fue considerada una fuente constante de errores dentro del kernel de Linux
  • En particular, había dos comportamientos problemáticos
    • El significado y comportamiento de la terminación NUL no eran intuitivos, por lo que era fácil equivocarse al usarla
    • Rellenaba con 0 el búfer de destino de forma redundante, generando un costo de rendimiento innecesario

El merge que concretó la eliminación

  • El viernes se realizó este merge, que eliminó la API strncpy
  • En el mismo merge también desapareció la última implementación de strncpy específica por arquitectura para per-CPU

APIs alternativas para usar en el código del kernel

  • En lugar de strncpy, ahora hay que elegir la función adecuada según el destino de copia y la condición de terminación
    • strscpy(): para destinos con terminación NUL
    • strscpy_pad(): para destinos con terminación NUL cuando se necesita padding con 0
    • strtomem_pad(): para campos de ancho fijo sin terminación NUL
    • memcpy_and_pad(): para copias limitadas con padding explícito
    • memcpy(): para copias de memoria cuando se conoce la longitud

1 comentarios

 
GN⁺ 3 시간 전
Comentarios en Hacker News
  • Antes se burlaban de que los desarrolladores del kernel de Linux, supuestamente entre los mejores programadores en C del mundo, no sabían crear tipos como stringbuffer o stringview, pero en esa época no existía el nivel de consenso que hay ahora sobre este tema, así que se entiende hasta cierto punto
    Quien ya había visto la dirección correcta era Dennis Ritchie, que en 1990 propuso un tipo de puntero gordo para C. Habría sido una adición perfecta si hubiera entrado en C99, y si el comité lo hubiera incluido, el mundo quizá sería bastante distinto
    En 2007 hubo una segunda oportunidad con el artículo “C's greatest mistake” de Walter Bright, que explicaba con más claridad la idea de slices/stringview, esencialmente la misma de Ritchie, pero tampoco logró entrar en C11. Ya llegamos a C23 y sigue sin existir; en cambio obtuvimos _Generic y VLA, así que al parecer toca hacer fiesta

    • El artículo de Walter Bright de 2007 está aquí: https://digitalmars.com/articles/C-biggest-mistake.html
      Buscando también vi un post de Reddit sobre el mismo tema, y la discusión tipo bike-shedding estuvo graciosa: https://www.reddit.com/r/C_Programming/comments/90uq7c/cs_bi...
      Me intriga por qué se diseñó así el comportamiento por el que los arreglos en C se degradan a punteros. Hay una explicación de que el objetivo era poder compilar código de B a C con cambios mínimos; al parecer, en B la declaración de arreglos en realidad definía tanto un puntero como un arreglo, e inicializaba ese puntero para que apuntara al primer elemento del arreglo
    • VLA fue degradado a característica opcional en C11, y me parece algo bueno
      El problema más grande ahora es que la biblioteca estándar de C sigue atada a la era de K&R, y ni siquiera funciones del lenguaje añadidas en C99, como pasar o devolver structs, se han reflejado en la API de la biblioteca estándar. Bastaría con que la biblioteca estándar tuviera structs de rango de puntero/tamaño y funciones de cadenas nuevas o actualizadas que los usaran para que la situación mejorara bastante
    • Enlace a la propuesta de Ritchie: https://web.archive.org/web/20150611114358/https://www.bell-...
    • Este es el patrón que más me molesta en el trabajo en equipo. Hay soluciones A, B y C, cada una con ventajas y desventajas, se discute durante dos semanas y al final no se elige nada
    • Eso solo muestra dónde están las prioridades de WG14
  • Dicen que strncpy dentro del kernel de Linux fue durante años una “fuente persistente de bugs” por su semántica contraintuitiva, el manejo de terminación NUL y el costo de rendimiento de rellenar el destino con ceros innecesariamente
    Cada vez que me pedían revisar código en C, buscaba strncpy, y siempre encontraba un bug ahí

  • Hay cosas que me han irritado durante 40 años. Las cadenas terminadas en NUL, y ahora también las cadenas que no son UTF-8 en entrada/salida
    También la convención de tratar el fin de línea como LF, CR o CRLF, y la forma de separar campos con pipes o comas. Si se hubieran usado caracteres ASCII no ambiguos como GS, FS y RS, la codificación/decodificación de fin de línea sería un problema de E/S, y HT/VT/CR/LF/FF podrían haberse quedado literalmente como códigos relacionados con la salida

    • He trabajado en un proyecto que transformaba datos enmarcados con separadores de campo/registro de ASCII, y fue realmente fácil de manejar
      Desaparece todo el problema sucio del escape que aparece con datos separados por comas, así que todo se vuelve mucho más simple
    • Unicode ahora ofrece más opciones. Está NL Next line, que parece venir de EBCDIC, además de LS Line separator y PS Paragraph separator, creados por Unicode
      El estándar Unicode dice que no solo CR, LF, CRLF y esos caracteres, sino también tabulación vertical y salto de página, deben tratarse como separadores de línea
    • UTF-8 funciona perfectamente bien en la entrada/salida estándar. Claro, siempre que no estemos hablando de Windows, que sigue atascado a principios de los 90 en cuanto a codificación de texto internacional
      Los finales de línea como LF, CR y CRLF también son una convención del sistema operativo, y es mejor que los lenguajes de programación no intenten “adivinar” cuál es el final de línea correcto. Eso crea más problemas de los que resuelve y, de nuevo, en general es un problema muy de Windows, así que Microsoft tendría que traer Windows al siglo actual
    • LF es lo que más sentido tiene, pero si es un archivo de texto, cualquiera de las opciones está bien. El problema es que CSV no es texto
      La última vez que tuve que manejar un archivo CSV en bash, internamente lo convertí a RS y FS para procesarlo
    • Yo diría que simplemente se use UTF-8 en todas partes
  • En lugar de strncpy, dicen que en el código del kernel de Linux hay que usar strscpy() para destinos terminados en NUL, strscpy_pad() para destinos terminados en NUL que requieren padding con ceros, strtomem_pad() para campos de ancho fijo no terminados en NUL, memcpy_and_pad() para copias delimitadas con padding explícito, y memcpy() para copias de memoria de longitud conocida
    Esto suena a pesadilla, y no sé si realmente tiene que ser tan complejo

    • La razón es el rendimiento. Una función segura universal que maneje la mayoría de estos casos inevitablemente sería más lenta por las ramas internas, y la elección de la función también expresa la intención del desarrollador
      Creo que es mejor que, al leer el código, la intención quede clara solo con la función elegida
    • Usar strncpy correctamente siempre fue complicado desde el principio
    • Como mínimo, podrían haber elegido nombres un poco mejores
  • Justo en este tipo de trabajo repetitivo y tedioso es donde ocurre el verdadero trabajo de la ingeniería de sistemas
    Estos grandes proyectos de infraestructura, que vuelven más confiable al kernel de Linux mientras lo mantienen utilizable durante todo el proceso, no se mueven en escalas de meses sino de décadas

    • Entiendo por qué termina siendo una escala de décadas. La cola larga de usuarios y dependencias es realmente larguísima
      Pero no sé si a esa velocidad se puede lograr un progreso significativo a largo plazo. No es tanto una queja como una paradoja de la infraestructura crítica
  • Es un trabajo impresionante y que te vuelve humilde. Sorprende que tanta gente haya contribuido a esto
    Las “nuevas funciones geniales” suelen recibir reconocimiento fácilmente, pero en algo tan fundamental como el kernel, quitar funciones malas puede ser todavía más importante
    Cuando dentro de 50 años llegue una era en la que la gente haya olvidado cómo leer código fuente, mientras se acumulan silenciosamente los residuos de Claude/Codex y se quema la mayor parte de la energía de la Tierra, da la impresión de que este tipo de cosas quedarán como leyendas de la “era fundacional”

    • Me hizo pensar en A Deepness in the Sky de Vernor Vinge. Ahí, alguien mantiene una nave espacial mediante arqueología de software
      Y además es la única persona que sabe qué es el Unix epoch
    • No creo que dentro de 50 años todo el mundo haya olvidado cómo entender código fuente. El deseo humano de saber cómo funcionan las cosas seguirá existiendo entonces
    • Creo que el código revoltijo generado por IA se volverá inmanejable mucho antes que eso
  • Creo que las cadenas terminadas en 0 fueron el mayor error en la historia de la computación. Las cadenas al estilo Pascal eran mucho más seguras

    • También hay puntos intermedios, como BSTR, adoptado por Visual Basic y después por COM
      Sigue siendo un puntero a un arreglo de caracteres terminado en 0, pero hay un campo de longitud justo antes del primer byte al que apunta el puntero. Asumiendo que no hay NUL embebidos, también es compatible con cadenas C, y las funciones del tipo BSTR pueden aprovechar el valor de longitud
    • Estoy algo de acuerdo, pero seguramente habría habido discusiones sobre el tipo de dato del campo de tamaño. Si no hubiera sido de longitud variable, todavía más; y si lo hubiera sido, eso habría traído otros problemas
      Durante un tiempo, incluso 16 bits pudieron parecer excesivos, y ahora 32 bits pueden parecer demasiado pocos. C, que se presenta como un lenguaje de “tipado fuerte”, en los lugares importantes en realidad es bastante laxo
    • Las cadenas terminadas en 0 fueron la base de una cantidad enorme de software útil. Llamarlo el mayor error de la computación es un poco exagerado
      Hace más de 30 años que no escribo código relacionado con Pascal, pero recuerdo vagamente que incluso entonces me parecía que su sistema de cadenas era demasiado incómodo de usar
    • ¿No se suponía que 255 caracteres debían ser suficientes para todos?
    • Tan malo como las líneas terminadas en salto de línea
  • Hay demasiado dolor y demasiado trabajo inútil por no tener ni siquiera un tipo de dato de cadena

    • Más exactamente, no es por no tener un tipo de cadena, sino por el dolor y el trabajo inútil que surgen al intentar rodear el hecho de que C no tiene un tipo de cadena
    • ¿Qué forma habría de introducir tipado fuerte aquí? Me imagino que también haría falta un gran refactor para que el código alrededor de strncpy use ese tipo y esas funciones, ¿no?
  • Me pregunto qué tuvo de tan difícil reescribir los usos de strncpy como para que haya tomado 6 años
    Me gustaría saber si era porque se usaba en todas partes, o si fue un trabajo de largo plazo en el que solo lo cambiaban cuando ya tocaba modificar el mismo archivo, o si hubo alguna otra dificultad

  • Me tocó lidiar con código en una app de Win32 que usaba cadenas rellenadas con espacios. La cadena de destino se rellenaba con espacios, pero el último byte seguía siendo un terminador nulo
    Para cosas como longitud y copiado, había que usar versiones especiales de las funciones de cadena. No sé por qué era así, pero como el codebase era tan viejo, quizá se originó en el comportamiento de estructuras de Pascal

    • Puede que fuera por cadenas provenientes de campos char de una base de datos SQL. Los campos char, a diferencia de varchar, se rellenan con espacios
    • Creo que la raíz de ese comportamiento no será Pascal, sino COBOL
    • También podría haber sido para evitar reasignaciones cuando cambiaba el tamaño de la cadena, o por alineación con las líneas de caché del CPU