1

Can some body help me what is wrong with my program. When i tried to run following program, I am getting the following error message:

hSetFileSize: invalid argument (Invalid argument)

import System.IO

main = do
    putStrLn "Enter file name (Including full path) to read"
    fileName <- getLine

    handle <- openFile fileName ReadMode
    sizeBeforeTrunc <- hFileSize handle
    content <- readFile fileName 

    putStrLn $ "Size of the file Before truncation is " ++ (show sizeBeforeTrunc) ++ " bytes" 
    putStrLn $ "Content of the file is " ++ content

    putStrLn "**************************************"
    let n = sizeBeforeTrunc `div` 2
    putStrLn $ "Truncating file to " ++ (show n) ++ " bytes"

    info1 <- hSetFileSize handle (toInteger 10)
    putStrLn $ show info1
    sizeAfterTrunc <- hFileSize handle

    putStrLn $ "Size of the file After truncation is " ++ (show sizeAfterTrunc) ++ " bytes" 
    putStrLn $ "Content of the file is " ++ content        

    hClose handle
Cactus
  • 27,075
  • 9
  • 69
  • 149
Hari Krishna
  • 3,658
  • 1
  • 36
  • 57

2 Answers2

3

You are opening the file for reading only; but truncating is a write operation.

What you can do instead is something like

main = do
    putStrLn "Enter file name (Including full path) to read"
    fileName <- getLine

    sizeBeforeTrunc <- withFile fileName ReadMode $ \h -> do
        sizeBeforeTrunc <- hFileSize h
        content <- hGetContents h

        putStrLn $ "Size of the file Before truncation is " ++ (show sizeBeforeTrunc) ++ " bytes" 
        putStrLn $ "Content of the file is " ++ content

    putStrLn "**************************************"
    let n = sizeBeforeTrunc `div` 2
    putStrLn $ "Truncating file to " ++ (show n) ++ " bytes"

    sizeAfterTrunc <- withFile fileName WriteMode $ \h -> do       
        info1 <- hSetFileSize h (toInteger 10)
        putStrLn $ show info1
        hFileSize h

    putStrLn $ "Size of the file After truncation is " ++ (show sizeAfterTrunc) ++ " bytes" 
    putStrLn $ "Content of the file is " ++ content            
Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 1
    Additional remark: using `openFile fileName WriteMode` will lead to an error in `content <- readFile fileName`, since GHC's runtime only allows either multiple readers or a single writer. Opening the file twice (once for getting the size and reading the content; then for truncating) seems more reasonable. – Zeta Mar 21 '16 at 06:50
  • @Zeta: good point, I've updated the answer to show a possible solution that first does the `ReadMode` operations, then the `WriteMode` ones. – Cactus Mar 21 '16 at 06:55
  • 2
    Also, I'd much rather use some bespoke strict IO functions (like [these](https://hackage.haskell.org/package/strict/docs/System-IO-Strict.html)) to read the file before truncating; the standard IO functions are, unfortunately, lazy. – Cactus Mar 21 '16 at 06:56
  • 1
    Instead of opening the file twice, you could open with `ReadWriteMode` (I think). +1 for using `withFile` instead of a `open`/`close` combo. – user2407038 Mar 21 '16 at 06:59
  • @user2407038: I'm happy to change it to use `ReadWriteMode` but only if at least one of us actually tries it out :) – Cactus Mar 21 '16 at 07:05
  • 1
    @user2407038 Err, `hGetContents` will put the handle in a semi-closed state. After all, there's a reason for [my last poem](http://stackoverflow.com/a/35844504/1139697). \*sigh\* Although one had to change it for this: *Whoopsy daisy, being lazy / tends to make file changes crazy. / Handle is gone / and all along / you ask yourself "what went wrong"? / Don't yet cry / keep on, try! / All the suffering soon pass by*. – Zeta Mar 21 '16 at 07:10
2

Note: This answer is written in literate Haskell. Save it with .lhs as extension and try it in GHCi or compile it.

> import System.IO

Changing a file isn't a read-only operation. You need to open the file for writing. However, this will lead to problems, since you may only have either a single writer or multiple readers. Since you use readFile your opening the file for reading, so you cannot simply change the open mode.

It gets easier if we refactor your code. We can immediately see two violations of don't-repeat-yourself, namely getting the size and telling the content. So let's fix that:

> putAndCount :: FileName -> String -> IO Int
> putAndCount fn msg = withFile fn ReadMode $ \handle -> do
>     size    <- hFileSize handle
>     content <- hGetContents handle
>     putStrLn $ "Size of the file " ++ msg ++ " is " ++ show size ++ " bytes"
>     putStrLn $ "Content of the file is " ++ content
>     return size

withFile makes sure that our handle is closed and cannot screw with further actions. Now let's write another function to change the size of a file without using a handle:

> setFileSize :: FileName -> Integer -> IO ()
> setFileSize fn s = withFile fn ReadWriteMode $ \handle -> 
>     hSetFileSize handle s

It's important to use ReadWriteMode and not WriteMode here, since WriteMode will truncate the file to zero bytes! That's the same behavior as in C's fopen, by the way.

Now we have all tools necessary to complete the task:

> main :: IO ()
> main = do
>     putStrLn "Enter file name (Including full path) to read"
>     fileName <- getLine
>
>     sizeBeforeTrunc <- putAndCount fileName "Before truncation"    
>     putStrLn "**************************************"
> 
>     let n = sizeBeforeTrunc `div` 2
>     putStrLn $ "Truncating file to " ++ (show n) ++ " bytes"    
>     setFileSize fileName n
>     putAndCount fileName "After truncation"
Zeta
  • 103,620
  • 13
  • 194
  • 236