Skip to content

fix Issue 12368 - std.file.write conflicts with std.stdio.write #2011

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

Merged
merged 4 commits into from
Mar 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions std/file.d
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@ Params:
buffer = data to be written to file

Throws: $(D FileException) on error.

See_also: $(XREF stdio,toFile)
*/
void write(R)(R name, const void[] buffer)
if (isInputRange!R && isSomeChar!(ElementEncodingType!R) || isSomeString!R)
Expand Down
244 changes: 240 additions & 4 deletions std/stdio.d
Original file line number Diff line number Diff line change
Expand Up @@ -2423,8 +2423,13 @@ $(D Range) that locks the file and allows fast writing to it.
{
private:
import std.range.primitives : ElementType, isInfinite, isInputRange;
FILE* fps_; // the shared file handle
_iobuf* handle_; // the unshared version of fps
// the shared file handle
FILE* fps_;

// the unshared version of fps
@property _iobuf* handle_() @trusted { return cast(_iobuf*) fps_; }

// the file's orientation (byte- or wide-oriented)
int orientation_;
public:
deprecated("accessing fps/handle/orientation directly can break LockingTextWriter integrity")
Expand All @@ -2443,7 +2448,6 @@ $(D Range) that locks the file and allows fast writing to it.
fps_ = f._p.handle;
orientation_ = fwide(fps_, 0);
FLOCK(fps_);
handle_ = cast(_iobuf*)fps_;
}

~this() @trusted
Expand All @@ -2452,7 +2456,6 @@ $(D Range) that locks the file and allows fast writing to it.
{
FUNLOCK(fps_);
fps_ = null;
handle_ = null;
}
}

Expand Down Expand Up @@ -2603,6 +2606,213 @@ See $(LREF byChunk) for an example.
return LockingTextWriter(this);
}

// An output range which optionally locks the file and puts it into
// binary mode (similar to rawWrite). Because it needs to restore
// the file mode on destruction, it is RefCounted on Windows.
struct BinaryWriterImpl(bool locking)
{
private:
FILE* fps;
string name;

version (Windows)
{
int fd, oldMode;
version (DIGITAL_MARS_STDIO)
ubyte oldInfo;
}

package:
this(ref File f)
{
import std.exception : enforce;

enforce(f._p && f._p.handle);
name = f._name;
fps = f._p.handle;
static if (locking)
FLOCK(fps);

version (Windows)
{
.fflush(fps); // before changing translation mode
fd = ._fileno(fps);
oldMode = ._setmode(fd, _O_BINARY);
version (DIGITAL_MARS_STDIO)
{
import core.atomic;

// @@@BUG@@@ 4243
oldInfo = __fhnd_info[fd];
atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT);
}
}
}

public:
~this()
{
if (!fps)
return;

version (Windows)
{
.fflush(fps); // before restoring translation mode
version (DIGITAL_MARS_STDIO)
{
// @@@BUG@@@ 4243
__fhnd_info[fd] = oldInfo;
}
._setmode(fd, oldMode);
}

FUNLOCK(fps);
fps = null;
}

void rawWrite(T)(in T[] buffer)
{
import std.conv : text;
import std.exception : errnoEnforce;

auto result =
.fwrite(buffer.ptr, T.sizeof, buffer.length, fps);
if (result == result.max) result = 0;
errnoEnforce(result == buffer.length,
text("Wrote ", result, " instead of ", buffer.length,
" objects of type ", T.stringof, " to file `",
name, "'"));
}

version (Windows)
{
@disable this(this);
}
else
{
this(this)
{
if (fps)
{
FLOCK(fps);
}
}
}

void put(T)(auto ref in T value)
if (!hasIndirections!T &&
!isInputRange!T)
{
rawWrite((&value)[0..1]);
}

void put(T)(in T[] array)
if (!hasIndirections!T &&
!isInputRange!T)
{
rawWrite(array);
}
}

/** Returns an output range that locks the file and allows fast writing to it.

Example:
Produce a grayscale image of the $(LUCKY Mandelbrot set)
in binary $(LUCKY Netpbm format) to standard output.
---
import std.algorithm, std.range, std.stdio;

void main()
{
enum size = 500;
writef("P5\n%d %d %d\n", size, size, ubyte.max);

iota(-1, 3, 2.0/size).map!(y =>
iota(-1.5, 0.5, 2.0/size).map!(x =>
cast(ubyte)(1+
recurrence!((a, n) => x + y*1i + a[n-1]^^2)(0+0i)
.take(ubyte.max)
.countUntil!(z => z.re^^2 + z.im^^2 > 4))
)
)
.copy(stdout.lockingBinaryWriter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Love this code example.

}
---
*/
auto lockingBinaryWriter()
{
alias LockingBinaryWriterImpl = BinaryWriterImpl!true;

version (Windows)
{
import std.typecons : RefCounted;
alias LockingBinaryWriter = RefCounted!LockingBinaryWriterImpl;
}
else
alias LockingBinaryWriter = LockingBinaryWriterImpl;

return LockingBinaryWriter(this);
}

unittest
{
import std.algorithm : copy, reverse;
static import std.file;
import std.exception : collectException;
import std.range : only, retro, put;
import std.string : format;

auto deleteme = testFilename();
scope(exit) collectException(std.file.remove(deleteme));
auto output = File(deleteme, "wb");
auto writer = output.lockingBinaryWriter();
auto input = File(deleteme, "rb");

T[] readExact(T)(T[] buf)
{
auto result = input.rawRead(buf);
assert(result.length == buf.length,
"Read %d out of %d bytes"
.format(result.length, buf.length));
return result;
}

// test raw values
ubyte byteIn = 42;
byteIn.only.copy(writer); output.flush();
ubyte byteOut = readExact(new ubyte[1])[0];
assert(byteIn == byteOut);

// test arrays
ubyte[] bytesIn = [1, 2, 3, 4, 5];
bytesIn.copy(writer); output.flush();
ubyte[] bytesOut = readExact(new ubyte[bytesIn.length]);
scope(failure) .writeln(bytesOut);
assert(bytesIn == bytesOut);

// test ranges of values
bytesIn.retro.copy(writer); output.flush();
bytesOut = readExact(bytesOut);
bytesOut.reverse();
assert(bytesIn == bytesOut);

// test string
"foobar".copy(writer); output.flush();
char[] charsOut = readExact(new char[6]);
assert(charsOut == "foobar");

// test ranges of arrays
only("foo", "bar").copy(writer); output.flush();
charsOut = readExact(charsOut);
assert(charsOut == "foobar");

// test that we are writing arrays as is,
// without UTF-8 transcoding
"foo"d.copy(writer); output.flush();
dchar[] dcharsOut = readExact(new dchar[3]);
assert(dcharsOut == "foo");
}

/// Get the size of the file, ulong.max if file is not searchable, but still throws if an actual error occurs.
@property ulong size() @safe
{
Expand Down Expand Up @@ -3912,6 +4122,32 @@ unittest
f.close();
}


/**
Writes an array or range to a file.
Shorthand for $(D data.copy(File(fileName, "wb").lockingBinaryWriter)).
Similar to $(XREF file,write), strings are written as-is,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should probably mention the crucial difference that toFile's parameter order is conducive to UFCS, especially if the file name convenience overload is going to stay.

rather than encoded according to the $(D File)'s $(WEB
en.cppreference.com/w/c/io#Narrow_and_wide_orientation,
orientation).
*/
void toFile(T)(T data, string fileName)
if (is(typeof(std.algorithm.mutation.copy(data, stdout.lockingBinaryWriter))))
{
std.algorithm.mutation.copy(data, File(fileName, "wb").lockingBinaryWriter);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why is this function better than:

r.copy(f);

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. File does not have a range interface, LockingTextWriter does
  2. LockingTextWriter does some things with strings to put them in the FILE*'s orientation
  3. Since we know it doesn't make sense to write pointers to files, we can have toFile write arrays and ranges of any number of dimensions (see the example).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File is not an output range. You have to use File.lockingTextWriter for that, which apparently deals with text.

edit:

Oops, didn't refresh.


unittest
{
static import std.file;

auto deleteme = testFilename();
scope(exit) { std.file.remove(deleteme); }

"Test".toFile(deleteme);
assert(std.file.readText(deleteme) == "Test");
}

/*********************
* Thrown if I/O errors happen.
*/
Expand Down