Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chinese file name garbled under Windows #1129

Closed
Tlioylc opened this issue Jul 6, 2022 · 2 comments
Closed

Chinese file name garbled under Windows #1129

Tlioylc opened this issue Jul 6, 2022 · 2 comments

Comments

@Tlioylc
Copy link

Tlioylc commented Jul 6, 2022

val filePath = "C:\Users\CJ2DWF3\Desktop\测试2222.txt"
val fileSys = FileSystem.SYSTEM
fileSys.write(filePath.toPath(false),false){
writeUtf8("测试测试测试测试")
}
Screenshot

@Tlioylc
Copy link
Author

Tlioylc commented Jul 6, 2022

we have tried to fix "create new file".here is the code that works fine
.\okio\src\mingwX64Main\kotlin\okio-WindowsPosixVariant

/*
 * Copyright (C) 2020 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okio

import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.ByteVarOf
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.MemScope
import kotlinx.cinterop.CArrayPointer
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.sizeOf
import kotlinx.cinterop.ptr
import kotlinx.cinterop.toKString
import okio.Path.Companion.toPath
import platform.posix.EACCES
import platform.posix.ENOENT
import platform.posix.FILE
import platform.posix.PATH_MAX
import platform.posix.S_IFDIR
import platform.posix.S_IFMT
import platform.posix.S_IFREG
import platform.posix._fullpath
import platform.posix._stat64
import platform.posix._wstat64
import platform.posix.errno
import platform.posix.fread
import platform.posix.free
import platform.posix.fwrite
import platform.posix.getenv
import platform.posix._wfopen
import platform.posix._wremove
import platform.posix._wrmdir
import platform.posix._wmkdir
import platform.posix.wchar_tVar
import platform.posix.memset
import platform.windows.CREATE_NEW
import platform.windows.CreateFileW
import platform.windows.MultiByteToWideChar
import platform.windows.CP_UTF8
import platform.windows.FILE_ATTRIBUTE_NORMAL
import platform.windows.FILE_SHARE_WRITE
import platform.windows.GENERIC_READ
import platform.windows.GENERIC_WRITE
import platform.windows.INVALID_HANDLE_VALUE
import platform.windows.MOVEFILE_REPLACE_EXISTING
import platform.windows.MoveFileExW
import platform.windows.OPEN_ALWAYS
import platform.windows.OPEN_EXISTING


internal actual val PLATFORM_TEMPORARY_DIRECTORY: Path
  get() {
    // Windows' built-in APIs check the TEMP, TMP, and USERPROFILE environment variables in order.
    // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppatha?redirectedfrom=MSDN
    val temp = getenv("TEMP")
    if (temp != null) return temp.toKString().toPath()

    val tmp = getenv("TMP")
    if (tmp != null) return tmp.toKString().toPath()

    val userProfile = getenv("USERPROFILE")
    if (userProfile != null) return userProfile.toKString().toPath()

    return "\\Windows\\TEMP".toPath()
  }

internal actual val PLATFORM_DIRECTORY_SEPARATOR = "\\"

internal actual fun PosixFileSystem.variantDelete(path: Path, mustExist: Boolean) {
  val pathString = path.toString()

  memScoped {
    val pathWchar = string2Wchar(pathString)
    if (_wremove(pathWchar) == 0) return

    // If remove failed with EACCES, it might be a directory. Try that.
    if (errno == EACCES) {
      if (_wrmdir(pathWchar) == 0) return
    }
  }
  if (errno == ENOENT) {
    if (mustExist) throw FileNotFoundException("no such file: $path")
    else return
  }

  throw errnoToIOException(EACCES)
}

internal actual fun PosixFileSystem.variantMkdir(dir: Path): Int {
  return memScoped {
    _wmkdir(string2Wchar(dir.toString()))
  }
}

internal actual fun PosixFileSystem.variantCanonicalize(path: Path): Path {
  // Note that _fullpath() returns normally if the file doesn't exist.
  val fullpath = _fullpath(null, path.toString(), PATH_MAX)
    ?: throw errnoToIOException(errno)
  try {
    val pathString = Buffer().writeNullTerminated(fullpath).readUtf8()
    memScoped {
      if (platform.posix._waccess(string2Wchar(pathString), 0) != 0 && errno == ENOENT) {
        throw FileNotFoundException("no such file")
      }
    }
    return pathString.toPath()
  } finally {
    free(fullpath)
  }
}

internal actual fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetadata? {
  return memScoped {
    val stat = alloc<_stat64>()
    if (_wstat64(string2Wchar(path.toString()), stat.ptr) != 0) {
      if (errno == ENOENT) return null
      throw errnoToIOException(errno)
    }
    return@memScoped FileMetadata(
      isRegularFile = stat.st_mode.toInt() and S_IFMT == S_IFREG,
      isDirectory = stat.st_mode.toInt() and S_IFMT == S_IFDIR,
      symlinkTarget = null,
      size = stat.st_size,
      createdAtMillis = stat.st_ctime * 1000L,
      lastModifiedAtMillis = stat.st_mtime * 1000L,
      lastAccessedAtMillis = stat.st_atime * 1000L
    )
  }
}

internal actual fun PosixFileSystem.variantMove(source: Path, target: Path) {
  if (MoveFileExW(source.toString(), target.toString(), MOVEFILE_REPLACE_EXISTING) == 0) {
    throw lastErrorToIOException()
  }
}

internal actual fun variantFread(
  target: CPointer<ByteVarOf<Byte>>,
  byteCount: UInt,
  file: CPointer<FILE>
): UInt {
  return fread(target, 1, byteCount.toULong(), file).toUInt()
}

internal actual fun variantFwrite(
  source: CPointer<ByteVar>,
  byteCount: UInt,
  file: CPointer<FILE>
): UInt {
  return fwrite(source, 1, byteCount.toULong(), file).toUInt()
}

internal actual fun PosixFileSystem.variantSource(file: Path): Source {
  val openFile: CPointer<FILE> = openFile(file.toString(), "rb")
    ?: throw errnoToIOException(errno)
  return FileSource(openFile)
}

internal actual fun PosixFileSystem.variantSink(file: Path, mustCreate: Boolean): Sink {
  // We're non-atomically checking file existence because Windows errors if we use the `x` flag along with `w`.
  if (mustCreate && exists(file)) throw IOException("$file already exists.")
  val openFile: CPointer<FILE> = openFile(file.toString(), "wb")
    ?: throw errnoToIOException(errno)
  return FileSink(openFile)
}

internal actual fun PosixFileSystem.variantAppendingSink(file: Path, mustExist: Boolean): Sink {
  // There is a `r+` flag which we could have used to force existence of [file] but this flag
  // doesn't allow opening for appending, and we don't currently have a way to move the cursor to
  // the end of the file. We are then forcing existence non-atomically.
  if (mustExist && !exists(file)) throw IOException("$file doesn't exist.")
  val openFile: CPointer<FILE> = openFile(file.toString(), "ab")
    ?: throw errnoToIOException(errno)
  return FileSink(openFile)
}

internal  fun openFile(filePath :String , mode : String) : CPointer<FILE>? {
  return memScoped {
    _wfopen(string2Wchar(filePath), string2Wchar(mode))
  }
}

internal  fun MemScope.string2Wchar(str : String) :CArrayPointer<wchar_tVar>{
  val nUnicodeLen = MultiByteToWideChar(CP_UTF8,0,str,-1,null,0)
  val pUnicode = allocArray<wchar_tVar>(nUnicodeLen + 1)
  memset(pUnicode,0, ((nUnicodeLen + 1)* sizeOf<wchar_tVar>()).toULong())
  MultiByteToWideChar(CP_UTF8,0,str,-1,pUnicode,nUnicodeLen)
  return pUnicode
}

internal actual fun PosixFileSystem.variantOpenReadOnly(file: Path): FileHandle {
  val openFile = CreateFileW(
    lpFileName = file.toString(),
    dwDesiredAccess = GENERIC_READ,
    dwShareMode = FILE_SHARE_WRITE,
    lpSecurityAttributes = null,
    dwCreationDisposition = OPEN_EXISTING,
    dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
    hTemplateFile = null
  )
  if (openFile == INVALID_HANDLE_VALUE) {
    throw lastErrorToIOException()
  }
  return WindowsFileHandle(false, openFile)
}

internal actual fun PosixFileSystem.variantOpenReadWrite(
  file: Path,
  mustCreate: Boolean,
  mustExist: Boolean
): FileHandle {
  require(!mustCreate || !mustExist) {
    "Cannot require mustCreate and mustExist at the same time."
  }

  val creationDisposition = when {
    mustCreate -> CREATE_NEW.toUInt()
    mustExist -> OPEN_EXISTING.toUInt()
    else -> OPEN_ALWAYS.toUInt()
  }

  val openFile = CreateFileW(
    lpFileName = file.toString(),
    dwDesiredAccess = GENERIC_READ or GENERIC_WRITE.toUInt(),
    dwShareMode = FILE_SHARE_WRITE,
    lpSecurityAttributes = null,
    dwCreationDisposition = creationDisposition,
    dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL,
    hTemplateFile = null
  )
  if (openFile == INVALID_HANDLE_VALUE) {
    throw lastErrorToIOException()
  }
  return WindowsFileHandle(true, openFile)
}

internal actual fun PosixFileSystem.variantCreateSymlink(source: Path, target: Path) {
  throw okio.IOException("Not supported")
}

I'm not a professional windows developer, not sure if there are hidden dangers. Can you give me some advice?

@swankjesse
Copy link
Member

We’re a bit pinned in by the APIs we’ve chosen to use here. In particular using the java.io.File APIs on JVM limits how much visibility we have on the underlying charset of the file names, and we’ve replicated that limitation to other platforms.

I recommend treating file names as implementation details and not UI, as much as possible. That is, file names should be under the program’s control and not the end-users. (This can’t always be the case, particularly with desktop software.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants